lib/write_xlsx/chart.rb in write_xlsx-0.51.0 vs lib/write_xlsx/chart.rb in write_xlsx-0.54.0

- old
+ new

@@ -56,18 +56,21 @@ # # ===line # Creates a Line style chart. See Writexlsx::Chart::Line. # # ===pie - # Creates an Pie style chart. See Writexlsx::Chart::Pie. + # Creates a Pie style chart. See Writexlsx::Chart::Pie. # # ===scatter - # Creates an Scatter style chart. See Writexlsx::Chart::Scatter. + # Creates a Scatter style chart. See Writexlsx::Chart::Scatter. # # ===stock - # Creates an Stock style chart. See Writexlsx::Chart::Stock. + # Creates a Stock style chart. See Writexlsx::Chart::Stock. # + # ===radar + # Creates a Radar style chart. See Writexlsx::Chart::Radar. + # # ==CHART FORMATTING # # The following chart formatting properties can be set for any chart object # that they apply to (and that are supported by WriteXLSX) such # as chart lines, column fill areas, plot area borders, markers and other @@ -454,10 +457,13 @@ require 'write_xlsx/chart/line' Chart::Line.new(subtype) when 'Pie' require 'write_xlsx/chart/pie' Chart::Pie.new(subtype) + when 'Radar' + require 'write_xlsx/chart/radar' + Chart::Radar.new(subtype) when 'Scatter' require 'write_xlsx/chart/scatter' Chart::Scatter.new(subtype) when 'Stock' require 'write_xlsx/chart/stock' @@ -476,11 +482,11 @@ @id = '' @series_index = 0 @style_id = 2 @axis_ids = [] @axis2_ids = [] - @has_category = false + @cat_has_num_fmt = false @requires_category = 0 @legend_position = 'right' @cat_axis_position = 'b' @val_axis_position = 'l' @formula_ids = {} @@ -788,56 +794,37 @@ # :min => 10, # :max => 80 # ) # def set_x_axis(params = {}) - @x_axis = convert_axis_args(params) + @x_axis = convert_axis_args(@x_axis, params) end # # Set the properties of the Y-axis. # # The set_y_axis() method is used to set properties of the Y axis. # The properties that can be set are the same as for set_x_axis, # def set_y_axis(params = {}) - @y_axis = - convert_axis_args( - {:major_gridlines => {:visible => 1}}. - merge(params) - ) + @y_axis = convert_axis_args(@y_axis, params) end # # Set the properties of the secondary X-axis. # def set_x2_axis(params = {}) - @x2_axis = - convert_axis_args( - { - :label_position => 'none', - :crossing => 'max', - :visible => 0 - }. - merge(params) - ) + @x2_axis = convert_axis_args(@x2_axis, params) end # # Set the properties of the secondary Y-axis. # def set_y2_axis(params = {}) - @y2_axis = - convert_axis_args( - { - :major_gridlines => {:visible => 0}, - :position => 'r', - :visible => 1 - }. - merge(params) - ) + @y2_axis = convert_axis_args(@y2_axis, params) end + # # Set the properties of the chart title. # # The set_title() method is used to set properties of the chart title. # @@ -855,10 +842,13 @@ data_id = get_data_id(name_formula, params[:data]) @title_name = name @title_formula = name_formula @title_data_id = data_id + + # Set the font properties if present. + @title_font = convert_font_args(params[:name_font]) end # # Set the properties of the chart legend. # @@ -1123,41 +1113,76 @@ end # # Convert user defined axis values into private hash values. # - def convert_axis_args(params) # :nodoc: - name, name_formula = process_names(params[:name], params[:name_formula]) + def convert_axis_args(axis, params) # :nodoc: + arg = (axis[:_defaults] || {}).merge(params) + name, name_formula = process_names(arg[:name], arg[:name_formula]) - data_id = get_data_id(name_formula, params[:data]) + data_id = get_data_id(name_formula, arg[:data]) axis = { - :_name => name, - :_formula => name_formula, - :_data_id => data_id, - :_reverse => params[:reverse], - :_min => params[:min], - :_max => params[:max], - :_minor_unit => params[:minor_unit], - :_major_unit => params[:major_unit], - :_minor_unit_type => params[:minor_unit_type], - :_major_unit_type => params[:major_unit_type], - :_log_base => params[:log_base], - :_crossing => params[:crossing], - :_position => params[:position], - :_label_position => params[:label_position], - :_major_gridlines => params[:major_gridlines] || {:visible => 1}, - :_visible => params[:visible] ? params[:visible] : 1 + :_defaults => axis[:_defaults], + :_name => name, + :_formula => name_formula, + :_data_id => data_id, + :_reverse => arg[:reverse], + :_min => arg[:min], + :_max => arg[:max], + :_minor_unit => arg[:minor_unit], + :_major_unit => arg[:major_unit], + :_minor_unit_type => arg[:minor_unit_type], + :_major_unit_type => arg[:major_unit_type], + :_log_base => arg[:log_base], + :_crossing => arg[:crossing], + :_position => arg[:position], + :_label_position => arg[:label_position], + :_num_format => arg[:num_format], + :_num_format_linked => arg[:num_format_linked], + :_visible => arg[:visible] || 1 } + # Map major_gridlines properties. + if arg[:major_gridlines] && ptrue?(arg[:major_gridlines][:visible]) + axis[:_major_gridlines] = { :_visible => arg[:major_gridlines][:visible] } + end + # Only use the first letter of bottom, top, left or right. axis[:_position] = axis[:_position].downcase[0, 1] if axis[:_position] + # Set the font properties if present. + axis[:_num_font] = convert_font_args(arg[:num_font]) + axis[:_name_font] = convert_font_args(arg[:name_font]) + axis end # + # Convert user defined font values into private hash values. + # + def convert_font_args(params) + return unless params + font = { + :_name => params[:name], + :_color => params[:color], + :_size => params[:size], + :_bold => params[:bold], + :_italic => params[:italic], + :_underline => params[:underline], + :_pitch_family => params[:pitch_family], + :_charset => params[:charset], + :_baseline => params[:baseline] || 0 + } + + # Convert font size units. + font[:_size] *= 100 if font[:_size] && font[:_size] != 0 + + font + end + + # # Convert and aref of row col values to a range formula. # def aref_to_formula(data) # :nodoc: # If it isn't an array ref it is probably a formula already. return data unless data.kind_of?(Array) @@ -1527,15 +1552,70 @@ @axis2_ids << id1 << id2 end end # + # Get the font style attributes from a font hash. + # + def get_font_style_attributes(font) + return [] unless font + + attributes = [] + attributes << 'sz' << font[:_size] if ptrue?(font[:_size]) + attributes << 'b' << font[:_bold] if font[:_bold] + attributes << 'i' << font[:_italic] if font[:_italic] + attributes << 'u' << 'sng' if font[:_underline] + + attributes << 'baseline' << font[:_baseline] + attributes + end + + # + # Get the font latin attributes from a font hash. + # + def get_font_latin_attributes(font) + return [] unless font + + attributes = [] + attributes << 'typeface' << font[:_name] if ptrue?(font[:_name]) + attributes << 'pitchFamily' << font[:_pitch_family] if font[:_pitch_family] + attributes << 'charset' << font[:_charset] if font[:_charset] + + attributes + end + # # Setup the default properties for a chart. # def set_default_properties # :nodoc: @chartarea = default_chartarea_property @plotarea = default_plotarea_property + + # Set the default axis properties. + @x_axis[:_defaults] = { + :num_format => 'General', + :major_gridlines => { :visible => 0 } + } + + @y_axis[:_defaults] = { + :num_format => 'General', + :major_gridlines => { :visible => 1 } + } + + @x2_axis[:_defaults] = { + :num_format => 'General', + :label_position => 'none', + :crossing => 'max', + :visible => 0 + } + + @y2_axis[:_defaults] = { + :num_format => 'General', + :major_gridlines => { :visible => 0 }, + :position => 'right', + :visible => 1 + } + set_x_axis set_y_axis set_x2_axis set_y2_axis @@ -1636,13 +1716,13 @@ # def write_chart # :nodoc: @writer.tag_elements('c:chart') do # Write the chart title elements. if title = @title_formula - write_title_formula(title, @title_data_id) + write_title_formula(title, @title_data_id, nil, @title_font) elsif title = @title_name - write_title_rich(title) + write_title_rich(title, nil, @title_font) end # Write the c:plotArea element. write_plot_area # Write the c:legend element. @@ -1836,20 +1916,19 @@ data = @formula_data[data_id] if data_id # Ignore <c:cat> elements for charts without category values. return unless formula - @has_category = true - @writer.tag_elements('c:cat') do # Check the type of cached data. type = get_data_type(data) if type == 'str' - @has_category = false + @cat_has_num_fmt = false # Write the c:strRef element. write_str_ref(formula, data, type) else + @cat_has_num_fmt = true # Write the c:numRef element. write_num_ref(formula, data, type) end end end @@ -1965,20 +2044,33 @@ write_delete(1) unless ptrue?(x_axis[:_visible]) # Write the c:axPos element. write_axis_pos(position, y_axis[:_reverse]) + + # Write the c:majorGridlines element. + write_major_gridlines(x_axis[:_major_gridlines]) + # Write the axis title elements. if title = x_axis[:_formula] - write_title_formula(title, @x_axis[:_data_id], horiz) + write_title_formula(title, @x_axis[:_data_id], horiz, @x_axis[:_name_font]) elsif title = x_axis[:_name] - write_title_rich(title, horiz) + write_title_rich(title, horiz, x_axis[:_name_font]) end + # Write the c:numFmt element. - write_num_fmt + write_cat_number_format(x_axis) + + # Write the c:majorTickMark element. + write_major_tick_mark(x_axis[:_major_tick_mark]) + # Write the c:tickLblPos element. write_tick_label_pos(x_axis[:_label_position]) + + # Write the axis font elements. + write_axis_font(x_axis[:_num_font]) + # Write the c:crossAx element. write_cross_axis(axis_ids[1]) if @show_crosses || ptrue?(x_axis[:_visible]) write_crossing(y_axis[:_crossing]) @@ -2023,21 +2115,27 @@ # Write the c:majorGridlines element. write_major_gridlines(y_axis[:_major_gridlines]) # Write the axis title elements. if title = y_axis[:_formula] - write_title_formula(title, y_axis[:_data_id], horiz) + write_title_formula(title, y_axis[:_data_id], horiz, y_axis[:_name_font]) elsif title = y_axis[:_name] - write_title_rich(title, horiz) + write_title_rich(title, horiz, y_axis[:_name_font]) end # Write the c:numberFormat element. - write_number_format + write_number_format(y_axis) + # Write the c:majorTickMark element. + write_major_tick_mark(y_axis[:_major_tick_mark]) + # Write the tickLblPos element. write_tick_label_pos(y_axis[:_label_position]) + # Write the axis font elements. + write_axis_font(y_axis[:_num_font]) + # Write the c:crossAx element. write_cross_axis(axis_ids[0]) write_crossing(x_axis[:_crossing]) @@ -2080,21 +2178,27 @@ # Write the c:axPos element. write_axis_pos(position, y_axis[:_reverse]) # Write the axis title elements. if title = x_axis[:_formula] - write_title_formula(title, y_axis[:_data_id], horiz) + write_title_formula(title, y_axis[:_data_id], horiz, x_axis[:_name_font]) elsif title = x_axis[:_name] - write_title_rich(title, horiz) + write_title_rich(title, horiz, x_axis[:_name_font]) end # Write the c:numberFormat element. - write_number_format + write_number_format(x_axis) + # Write the c:majorTickMark element. + write_major_tick_mark(x_axis[:_major_tick_mark]) + # Write the c:tickLblPos element. write_tick_label_pos(x_axis[:_label_position]) + # Write the axis font elements. + write_axis_font(x_axis[:_num_font]) + # Write the c:crossAx element. write_cross_axis(axis_ids[1]) write_crossing(y_axis[:_crossing]) @@ -2173,18 +2277,23 @@ # Write the c:axPos element. write_axis_pos(position, y_axis[:reverse]) # Write the axis title elements. if title = x_axis[:_formula] - write_title_formula(title, x_axis[:_data_id]) + write_title_formula(title, x_axis[:_data_id], nil, x_axis[:_name_font]) elsif title = x_axis[:_name] - write_title_rich(title) + write_title_rich(title, nil, x_axis[:_name_font]) end # Write the c:numFmt element. - write_num_fmt('dd/mm/yyyy') + write_number_format(x_axis) + # Write the c:majorTickMark element. + write_major_tick_mark(x_axis[:_major_tick_mark]) + # Write the c:tickLblPos element. write_tick_label_pos(x_axis[:_label_position]) + # Write the font elements. + write_axis_font(x_axis[:_num_font]) # Write the c:crossAx element. write_cross_axis(axis_ids[1]) if @show_crosses || ptrue?(x_axis[:_visible]) write_crossing(y_axis[:_crossing]) @@ -2301,28 +2410,82 @@ @writer.empty_tag('c:axPos', attributes) end # - # Write the <c:numFmt> element. + # Write the <c:numberFormat> element. Note: It is assumed that if a user + # defined number format is supplied (i.e., non-default) then the sourceLinked + # attribute is 0. The user can override this if required. # - def write_num_fmt(format_code = nil) # :nodoc: - format_code ||= 'General' + + def write_number_format(axis) # :nodoc: + format_code = axis[:_num_format] source_linked = 1 - # These elements are only required for charts with categories. - return unless @has_category + # Check if a user defined number format has been set. + if axis[:_defaults] && format_code != axis[:_defaults][:num_format] + source_linked = 0 + end + # User override of sourceLinked. + if ptrue?(axis[:_num_format_linked]) + source_linked = 1 + end + attributes = [ 'formatCode', format_code, 'sourceLinked', source_linked ] @writer.empty_tag('c:numFmt', attributes) end # + # Write the <c:numFmt> element. Special case handler for category axes which + # don't always have a number format. + # + def write_cat_number_format(axis) + format_code = axis[:_num_format] + source_linked = 1 + default_format = true + + # Check if a user defined number format has been set. + if axis[:_defaults] && format_code != axis[:_defaults][:num_format] + source_linked = 0 + default_format = false + end + + # User override of linkedSource. + if axis[:_num_format_linked] + source_linked = 1 + end + + # Skip if cat doesn't have a num format (unless it is non-default). + if !@cat_has_num_fmt && default_format + return '' + end + + attributes = [ + 'formatCode', format_code, + 'sourceLinked', source_linked, + ] + + @writer.empty_tag('c:numFmt', attributes) + end + + # + # Write the <c:majorTickMark> element. + # + def write_major_tick_mark(val) + return unless ptrue?(val) + + attributes = ['val', val] + + @writer.empty_tag('c:majorTickMark', attributes) + end + + # # Write the <c:tickLblPos> element. # def write_tick_label_pos(val) # :nodoc: val ||= 'nextTo' val = 'nextTo' if val == 'next_to' @@ -2389,34 +2552,18 @@ end # # Write the <c:majorGridlines> element. # - def write_major_gridlines(options = {}) # :nodoc: - return unless ptrue?(options[:visible]) + def write_major_gridlines(gridlines) # :nodoc: + return unless gridlines + return unless ptrue?(gridlines[:_visible]) @writer.empty_tag('c:majorGridlines') end # - # Write the <c:numberFormat> element. - # - # TODO. Merge/replace with _write_num_fmt. - # - def write_number_format # :nodoc: - format_code = 'General' - source_linked = 1 - - attributes = [ - 'formatCode', format_code, - 'sourceLinked', source_linked - ] - - @writer.empty_tag('c:numFmt', attributes) - end - - # # Write the <c:crossBetween> element. # def write_cross_between # :nodoc: val = @cross_between || 'between' @@ -2546,11 +2693,11 @@ # Write the <c:plotVisOnly> element. # def write_plot_vis_only # :nodoc: val = 1 - # Ignore this element if we are plitting hidden data. + # Ignore this element if we are plotting hidden data. return if @show_hidden_data attributes = ['val', val] @writer.empty_tag('c:plotVisOnly', attributes) @@ -2608,38 +2755,38 @@ end # # Write the <c:title> element for a rich string. # - def write_title_rich(title, horiz = nil) # :nodoc: + def write_title_rich(title, horiz = nil, font = nil) # :nodoc: @writer.tag_elements('c:title') do # Write the c:tx element. - write_tx_rich(title, horiz) + write_tx_rich(title, horiz, font) # Write the c:layout element. write_layout end end # # Write the <c:title> element for a rich string. # - def write_title_formula(title, data_id, horiz = nil) # :nodoc: + def write_title_formula(title, data_id, horiz = nil, font = nil) # :nodoc: @writer.tag_elements('c:title') do # Write the c:tx element. write_tx_formula(title, data_id) # Write the c:layout element. write_layout # Write the c:txPr element. - write_tx_pr(horiz) + write_tx_pr(horiz, font) end end # # Write the <c:tx> element. # - def write_tx_rich(title, horiz) # :nodoc: - @writer.tag_elements('c:tx') { write_rich(title, horiz) } + def write_tx_rich(title, horiz, font = nil) # :nodoc: + @writer.tag_elements('c:tx') { write_rich(title, horiz, font) } end # # Write the <c:tx> element with a simple value such as for series names. # @@ -2657,18 +2804,18 @@ end # # Write the <c:rich> element. # - def write_rich(title, horiz) # :nodoc: + def write_rich(title, horiz, font) # :nodoc: @writer.tag_elements('c:rich') do # Write the a:bodyPr element. write_a_body_pr(horiz) # Write the a:lstStyle element. write_a_lst_style # Write the a:p element. - write_a_p_rich(title) + write_a_p_rich(title, font) end end # # Write the <a:bodyPr> element. @@ -2695,50 +2842,65 @@ end # # Write the <a:p> element for rich string titles. # - def write_a_p_rich(title) # :nodoc: + def write_a_p_rich(title, font) # :nodoc: @writer.tag_elements('a:p') do # Write the a:pPr element. - write_a_p_pr_rich + write_a_p_pr_rich(font) # Write the a:r element. - write_a_r(title) + write_a_r(title, font) end end # # Write the <a:p> element for formula titles. # - def write_a_p_formula # :nodoc: + def write_a_p_formula(font = nil) # :nodoc: @writer.tag_elements('a:p') do # Write the a:pPr element. - write_a_p_pr_formula + write_a_p_pr_formula(font) # Write the a:endParaRPr element. write_a_end_para_rpr end end # # Write the <a:pPr> element for rich string titles. # - def write_a_p_pr_rich # :nodoc: - @writer.tag_elements('a:pPr') { write_a_def_rpr } + def write_a_p_pr_rich(font) # :nodoc: + @writer.tag_elements('a:pPr') { write_a_def_rpr(font) } end # # Write the <a:pPr> element for formula titles. # - def write_a_p_pr_formula # :nodoc: - @writer.tag_elements('a:pPr') { write_a_def_rpr } + def write_a_p_pr_formula(font) # :nodoc: + @writer.tag_elements('a:pPr') { write_a_def_rpr(font) } end # # Write the <a:defRPr> element. # - def write_a_def_rpr # :nodoc: - @writer.empty_tag('a:defRPr') + def write_a_def_rpr(font = nil) # :nodoc: + style_attributes = get_font_style_attributes(font) + latin_attributes = get_font_latin_attributes(font) + has_color = ptrue?(font) && ptrue?(font[:_color]) + + if !latin_attributes.empty? || has_color + @writer.tag_elements('a:defRPr', style_attributes) do + if has_color + write_a_solid_fill(:color => font[:_color]) + end + if !latin_attributes.empty? + write_a_latin(latin_attributes) + end + end + else + @writer.empty_tag('a:defRPr', style_attributes) + end end # # Write the <a:endParaRPr> element. # @@ -2751,28 +2913,44 @@ end # # Write the <a:r> element. # - def write_a_r(title) # :nodoc: + def write_a_r(title, font) # :nodoc: @writer.tag_elements('a:r') do # Write the a:rPr element. - write_a_r_pr + write_a_r_pr(font) # Write the a:t element. write_a_t(title) end end # # Write the <a:rPr> element. # - def write_a_r_pr # :nodoc: + def write_a_r_pr(font) # :nodoc: lang = 'en-US' - attributes = ['lang', lang] + style_attributes = get_font_style_attributes(font) + latin_attributes = get_font_latin_attributes(font) + has_color = ptrue?(font) && ptrue?(font[:_color]) - @writer.empty_tag('a:rPr', attributes) + # Add the lang type to the attributes. + style_attributes.unshift(lang).unshift('lang') + + if !latin_attributes.empty? || has_color + @writer.tag_elements('a:rPr', style_attributes) do + if has_color + write_a_solid_fill(:color => font[:_color]) + end + if !latin_attributes.empty? + write_a_latin(latin_attributes) + end + end + else + @writer.empty_tag('a:rPr', style_attributes) + end end # # Write the <a:t> element. # @@ -2781,18 +2959,18 @@ end # # Write the <c:txPr> element. # - def write_tx_pr(horiz) # :nodoc: + def write_tx_pr(horiz, font) # :nodoc: @writer.tag_elements('c:txPr') do # Write the a:bodyPr element. write_a_body_pr(horiz) # Write the a:lstStyle element. write_a_lst_style # Write the a:p element. - write_a_p_formula + write_a_p_formula(font) end end # # Write the <c:marker> element. @@ -3231,10 +3409,33 @@ attributes = ['val', val] @writer.empty_tag('c:invertIfNegative', attributes) end - def nil_or_max?(val) + # + # Write the axis font elements. + # + def write_axis_font(font) # :nodoc: + return unless font + + @writer.tag_elements('c:txPr') do + @writer.empty_tag('a:bodyPr') + write_a_lst_style + @writer.tag_elements('a:p') do + write_a_p_pr_rich(font) + write_a_end_para_rpr + end + end + end + + # + # Write the <a:latin> element. + # + def write_a_latin(args) # :nodoc: + @writer.empty_tag('a:latin', args) + end + + def nil_or_max?(val) # :nodoc: val.nil? || val == 'max' end end end