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

- old
+ new

@@ -366,69 +366,104 @@ # Data labels can be added to a chart series to indicate the values of # the plotted data points. # # The following properties can be set for data_labels formats in a chart. # - # value - # category - # series_name + # :value + # :category + # :series_name + # :position + # :leader_lines + # :percentage + # # The value property turns on the Value data label for a series. # # chart.add_series( # :values => '=Sheet1!$B$1:$B$5', - # :data_labels => { value => 1 } + # :data_labels => { :value => 1 } # ) # The category property turns on the Category Name data label for a series. # # chart.add_series( # :values => '=Sheet1!$B$1:$B$5', - # :data_labels => { category => 1 } + # :data_labels => { :category => 1 } # ) # The series_name property turns on the Series Name data label for a series. # # chart.add_series( # :values => '=Sheet1!$B$1:$B$5', - # :data_labels => { series_name => 1 } + # :data_labels => { :series_name => 1 } # ) - # Other formatting options + # The C<position> property is used to position the data label for a series. # - # Other formatting options will be added in time. If there is a feature that - # you would like to see included drop me a line. + # chart.add_series( + # :values => '=Sheet1!$B$1:$B$5', + # :data_labels => { :value => 1, :position => 'center' } + # ) # + # Valid positions are: + # + # :center + # :right + # :left + # :top + # :bottom + # :above # Same as top + # :below # Same as bottom + # :inside_end # Pie chart mainly. + # :outside_end # Pie chart mainly. + # :best_fit # Pie chart mainly. + # + # The C<percentage> property is used to turn on the I<Percentage> + # for the data label for a series. It is mainly used for pie charts. + # + # chart.add_series( + # :values => '=Sheet1!$B$1:$B$5', + # :data_labels => { :percentage => 1 } + # ) + # + # The C<leader_lines> property is used to turn on I<Leader Lines> + # for the data label for a series. It is mainly used for pie charts. + # + # chart.add_series( + # :values => '=Sheet1!$B$1:$B$5', + # :data_labels => { :value => 1, :leader_lines => 1 } + # ) + # class Chart include Writexlsx::Utility - attr_accessor :id # :nodoc: - attr_writer :index, :palette # :nodoc: + attr_accessor :id, :name # :nodoc: + attr_writer :index, :palette, :protection # :nodoc: attr_reader :embedded, :formula_ids, :formula_data # :nodoc: # # Factory method for returning chart objects based on their class type. # - def self.factory(chart_subclass) # :nodoc: - case chart_subclass.downcase.capitalize + def self.factory(current_subclass, subtype = nil) # :nodoc: + case current_subclass.downcase.capitalize when 'Area' require 'write_xlsx/chart/area' - Chart::Area.new + Chart::Area.new(subtype) when 'Bar' require 'write_xlsx/chart/bar' - Chart::Bar.new + Chart::Bar.new(subtype) when 'Column' require 'write_xlsx/chart/column' - Chart::Column.new + Chart::Column.new(subtype) when 'Line' require 'write_xlsx/chart/line' - Chart::Line.new + Chart::Line.new(subtype) when 'Pie' require 'write_xlsx/chart/pie' - Chart::Pie.new + Chart::Pie.new(subtype) when 'Scatter' require 'write_xlsx/chart/scatter' - Chart::Scatter.new + Chart::Scatter.new(subtype) when 'Stock' require 'write_xlsx/chart/stock' - Chart::Stock.new + Chart::Stock.new(subtype) end end def initialize(subtype) # :nodoc: @writer = Package::XMLWriterSimple.new @@ -437,13 +472,15 @@ @sheet_type = 0x0200 @orientation = 0x0 @series = [] @embedded = 0 @id = '' + @series_index = 0 @style_id = 2 @axis_ids = [] - @has_category = 0 + @axis2_ids = [] + @has_category = false @requires_category = 0 @legend_position = 'right' @cat_axis_position = 'b' @val_axis_position = 'l' @formula_ids = {} @@ -451,10 +488,16 @@ @horiz_cat_axis = 0 @horiz_val_axis = 1 @protection = 0 @x_axis = {} @y_axis = {} + @x2_axis = {} + @y2_axis = {} + @name = '' + @show_blanks = 'gap' + @show_hidden_data = false + @show_crosses = true set_default_properties end def set_xml_writer(filename) # :nodoc: @@ -466,30 +509,28 @@ # def assemble_xml_file # :nodoc: @writer.xml_decl # Write the c:chartSpace element. - write_chart_space + write_chart_space do - # Write the c:lang element. - write_lang + # Write the c:lang element. + write_lang - # Write the c:style element. - write_style + # Write the c:style element. + write_style - # Write the c:protection element. - write_protection + # Write the c:protection element. + write_protection - # Write the c:chart element. - write_chart + # Write the c:chart element. + write_chart - # Write the c:printSettings element. - write_print_settings if @embedded + # Write the c:printSettings element. + write_print_settings if @embedded && @embedded != 0 + end - # Close the worksheet tag. - @writer.end_tag( 'c:chartSpace') - # Close the XML writer object and filehandle. @writer.crlf @writer.close end @@ -627,10 +668,14 @@ labels = get_labels_properties(params[:data_labels]) # Set the "invert if negative" fill property. invert_if_neg = params[:invert_if_negative] + # Set the secondary axis properties. + x2_axis = params[:x2_axis] + y2_axis = params[:y2_axis] + # Add the user supplied data to the internal structures. @series << { :_values => values, :_categories => categories, :_name => name, @@ -641,11 +686,13 @@ :_line => line, :_fill => fill, :_marker => marker, :_trendline => trendline, :_labels => labels, - :_invert_if_neg => invert_if_neg + :_invert_if_neg => invert_if_neg, + :_x2_axis => x2_axis, + :_y2_axis => y2_axis } end # # Set the properties of the X-axis. @@ -740,25 +787,58 @@ # :name => 'Quarterly results', # :min => 10, # :max => 80 # ) # - def set_x_axis(params) + def set_x_axis(params = {}) @x_axis = convert_axis_args(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(params) + def set_y_axis(params = {}) + @y_axis = + convert_axis_args( + {:major_gridlines => {:visible => 1}}. + merge(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) + ) + 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) + ) + end + # # Set the properties of the chart title. # # The set_title() method is used to set properties of the chart title. # # chart.set_title( :name => 'Year End Results' ) @@ -954,54 +1034,97 @@ style_id = 2 if style_id < 0 || style_id > 42 @style_id = style_id end # + # Set the option for displaying blank data in a chart. The default is 'gap'. + # + # The show_blanks_as method controls how blank data is displayed in a chart. + # + # chart.show_blanks_as('span') + # + # The available options are: + # + # gap # Blank data is show as a gap. The default. + # zero # Blank data is displayed as zero. + # span # Blank data is connected with a line. + # + def show_blanks_as(option) + return unless option + + unless [:gap, :zero, :span].include?(option.to_sym) + raise "Unknown show_blanks_as() option '#{option}'\n" + end + + @show_blanks = option + end + + # + # Display data in hidden rows or columns on the chart. + # + def show_hidden_data + @show_hidden_data = true + end + + # # Setup the default configuration data for an embedded chart. # def set_embedded_config_data @embedded = 1 # TODO. We may be able to remove this after refactoring. - @chartarea = { - :_visible => 1, - :_fg_color_index => 0x4E, - :_fg_color_rgb => 0xFFFFFF, - :_bg_color_index => 0x4D, - :_bg_color_rgb => 0x000000, - :_area_pattern => 0x0001, - :_area_options => 0x0001, - :_line_pattern => 0x0000, - :_line_weight => 0x0000, - :_line_color_index => 0x4D, - :_line_color_rgb => 0x000000, - :_line_options => 0x0009 - } - + @chartarea = default_chartarea_property_for_embedded end - # - # Write the <c:barChart> element. - # - def write_bar_chart # :nodoc: - subtype = @subtype - subtype = 'percentStacked' if subtype == 'percent_stacked' + # + # Write the <c:barChart> element. + # + def write_bar_chart(params) # :nodoc: + if ptrue?(params[:primary_axes]) + series = get_primary_axes_series + else + series = get_secondary_axes_series + end + return if series.empty? - @writer.tag_elements('c:barChart') do - # Write the c:barDir element. - write_bar_dir - # Write the c:grouping element. - write_grouping(subtype) - # Write the series elements. - write_series - end + subtype = @subtype + subtype = 'percentStacked' if subtype == 'percent_stacked' + + @writer.tag_elements('c:barChart') do + # Write the c:barDir element. + write_bar_dir + # Write the c:grouping element. + write_grouping(subtype) + # Write the c:ser elements. + series.each {|s| write_ser(s)} + + # write the c:marker element. + write_marker_value + + # write the c:overlap element. + write_overlap if @subtype =~ /stacked/ + + # Write the c:axId elements + write_axis_ids(params) end + end private # + # retun primary/secondary series by :primary_axes flag + # + def axes_series(params) + if params[:primary_axes] != 0 + primary_axes_series + else + secondary_axes_series + end + 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]) @@ -1019,11 +1142,13 @@ :_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] + :_label_position => params[:label_position], + :_major_gridlines => params[:major_gridlines] || {:visible => 1}, + :_visible => params[:visible] ? params[:visible] : 1 } # Only use the first letter of bottom, top, left or right. axis[:_position] = axis[:_position].downcase[0, 1] if axis[:_position] @@ -1112,11 +1237,11 @@ # def get_color(color) # :nodoc: # Convert a HTML style #RRGGBB color. if color and color =~ /^#[0-9a-fA-F]{6}$/ color = color.sub(/^#/, '') - return color.upperca + return color.upcase end index = Format.get_color(color) # Set undefined colors to black. @@ -1233,15 +1358,11 @@ # Check the dash type. dash_type = line[:dash_type] if dash_type - if dash_types[dash_type] - line[dash_type] = dash_types[dash_type] - else - raise "Unknown dash type '#{dash_type}'\n" - end + line[:dash_type] = value_or_raise(dash_types, dash_type, 'dash type') end line[:_defined] = 1 line @@ -1280,30 +1401,25 @@ :plus => 'plus', :picture => 'picture' } # Check for valid types. - marker_type = marker[type] + marker_type = marker[:type] if marker_type - marker[automatic] = 1 if marker_type == 'automatic' - - if types[marker_type] - marker[type] = types[marker_type] - else - raise "Unknown marker type '#{marker_type}'\n" - end + marker[:automatic] = 1 if marker_type == 'automatic' + marker[:type] = value_or_raise(types, marker_type, 'maker type') end # Set the line properties for the marker.. - line = get_line_properties(marker[line]) + line = get_line_properties(marker[:line]) # Allow 'border' as a synonym for 'line'. - line = get_line_properties(marker[border]) if marker[border] + line = get_line_properties(marker[:border]) if marker[:border] # Set the fill properties for the marker. - fill = get_fill_properties(marker[fill]) + fill = get_fill_properties(marker[:fill]) marker[:_line] = line marker[:_fill] = fill marker @@ -1323,26 +1439,22 @@ :polynomial => 'poly', :power => 'power' } # Check the trendline type. - trend_type = trendline[type] + trend_type = trendline[:type] - if types[trend_type] - trendline[type] = types[trend_type] - else - raise "Unknown trendline type '#{trend_type}'\n" - end + trendline[:type] = value_or_raise(types, trend_type, 'trendline type') # Set the line properties for the trendline.. - line = get_line_properties(trendline[line]) + line = get_line_properties(trendline[:line]) # Allow 'border' as a synonym for 'line'. - line = get_line_properties(trendline[border]) if trendline[border] + line = get_line_properties(trendline[:border]) if trendline[:border] # Set the fill properties for the trendline. - fill = get_fill_properties(trendline[fill]) + fill = get_fill_properties(trendline[:fill]) trendline[:_line] = line trendline[:_fill] = fill return trendline @@ -1352,32 +1464,87 @@ # Convert user defined labels properties to the structure required internally. # def get_labels_properties(labels) # :nodoc: return nil unless labels - return labels + position = labels[:position] + if position.nil? || position.empty? + labels.delete(:position) + else + # Map user defined label positions to Excel positions. + positions = { + :center => 'ctr', + :right => 'r', + :left => 'l', + :top => 't', + :above => 't', + :bottom => 'b', + :below => 'b', + :inside_end => 'inEnd', + :outside_end => 'outEnd', + :best_fit => 'bestFit' + } + + labels[:position] = value_or_raise(positions, position, 'label position') + end + + labels end + def value_or_raise(hash, key, msg) + raise "Unknown #{msg} '#{key}'" unless hash[key.to_sym] + hash[key.to_sym] + end + # - # Add a unique id for an axis. + # Returns series which use the primary axes. # - def add_axis_id # :nodoc: - chart_id = 1 + @id - axis_count = 1 + @axis_ids.size + def get_primary_axes_series + @series.reject {|s| s[:_y2_axis]} + end + alias :primary_axes_series :get_primary_axes_series - axis_id = sprintf('5%03d%04d', chart_id, axis_count) + # + # Returns series which use the secondary axes. + # + def get_secondary_axes_series + @series.select {|s| s[:_y2_axis]} + end + alias :secondary_axes_series :get_secondary_axes_series - @axis_ids << axis_id + # + # Add a unique ids for primary or secondary axis. + # + def add_axis_ids(params) # :nodoc: + chart_id = 1 + @id + axis_count = 1 + @axis2_ids.size + @axis_ids.size - axis_id + id1 = sprintf('5%03d%04d', chart_id, axis_count) + id2 = sprintf('5%03d%04d', chart_id, axis_count + 1) + + if ptrue?(params[:primary_axes]) + @axis_ids << id1 << id2 + else + @axis2_ids << id1 << id2 + end end # # Setup the default properties for a chart. # def set_default_properties # :nodoc: - @chartarea = { + @chartarea = default_chartarea_property + @plotarea = default_plotarea_property + set_x_axis + set_y_axis + + set_x2_axis + set_y2_axis + end + + def default_chartarea_property + { :_visible => 0, :_fg_color_index => 0x4E, :_fg_color_rgb => 0xFFFFFF, :_bg_color_index => 0x4D, :_bg_color_rgb => 0x000000, @@ -1387,12 +1554,26 @@ :_line_weight => 0xFFFF, :_line_color_index => 0x4D, :_line_color_rgb => 0x000000, :_line_options => 0x0008 } + end - @plotarea = { + def default_chartarea_property_for_embedded + default_chartarea_property. + merge( + :_visible => 1, + :_area_pattern => 0x0001, + :_area_options => 0x0001, + :_line_pattern => 0x0000, + :_line_weight => 0x0000, + :_line_options => 0x0009 + ) + end + + def default_plotarea_property + { :_visible => 1, :_fg_color_index => 0x16, :_fg_color_rgb => 0xC0C0C0, :_bg_color_index => 0x4F, :_bg_color_rgb => 0x000000, @@ -1408,22 +1589,23 @@ # # Write the <c:chartSpace> element. # def write_chart_space # :nodoc: - schema = 'http://schemas.openxmlformats.org/' - xmlns_c = schema + 'drawingml/2006/chart' - xmlns_a = schema + 'drawingml/2006/main' - xmlns_r = schema + 'officeDocument/2006/relationships' + @writer.tag_elements('c:chartSpace', chart_space_attributes) do + yield + end + end - attributes = [ - 'xmlns:c', xmlns_c, - 'xmlns:a', xmlns_a, - 'xmlns:r', xmlns_r - ] - - @writer.start_tag('c:chartSpace', attributes) + # for <c:chartSpace> element. + def chart_space_attributes # :nodoc: + schema = 'http://schemas.openxmlformats.org/' + [ + 'xmlns:c', "#{schema}drawingml/2006/chart", + 'xmlns:a', "#{schema}drawingml/2006/main", + 'xmlns:r', "#{schema}officeDocument/2006/relationships" + ] end # # Write the <c:lang> element. # @@ -1465,29 +1647,73 @@ write_plot_area # Write the c:legend element. write_legend # Write the c:plotVisOnly element. write_plot_vis_only + + # Write the c:dispBlanksAs element. + write_disp_blanks_as end end # + # Write the <c:dispBlanksAs> element. + # + def write_disp_blanks_as + val = @show_blanks + + # Ignore the default value. + return if val == 'gap' + + attributes = ['val', val] + + @writer.empty_tag('c:dispBlanksAs', attributes) + end + + # # Write the <c:plotArea> element. # - def write_plot_area # :nodoc: + def write_plot_area # :nodoc: + write_plot_area_base + end + + def write_plot_area_base(type = nil) # :nodoc: @writer.tag_elements('c:plotArea') do # Write the c:layout element. write_layout - # Write the subclass chart type element. - write_chart_type - # Write the c:catAx element. - write_cat_axis - # Write the c:catAx element. - write_val_axis + # Write the subclass chart type elements for primary and secondary axes. + write_chart_type(:primary_axes => 1) + write_chart_type(:primary_axes => 0) + + # Write the c:catAx elements for series using primary axes. + params = { + :x_axis => @x_axis, + :y_axis => @y_axis, + :axis_ids => @axis_ids + } + write_cat_or_date_axis(params, type) + write_val_axis(params) + + # Write c:valAx and c:catAx elements for series using secondary axes. + params = { + :x_axis => @x2_axis, + :y_axis => @y2_axis, + :axis_ids => @axis2_ids + } + write_val_axis(params) + write_cat_or_date_axis(params, type) end end + def write_cat_or_date_axis(params, type) + if type == :stock + write_date_axis(params) + else + write_cat_axis(params) + end + end + # # Write the <c:layout> element. # def write_layout # :nodoc: @writer.empty_tag('c:layout') @@ -1509,12 +1735,12 @@ end # # Write the series elements. # - def write_series # :nodoc: - write_series_base { nil } + def write_series(series) # :nodoc: + write_ser(series) end def write_series_base # Write each series with subelements. index = 0 @@ -1540,11 +1766,14 @@ end # # Write the <c:ser> element. # - def write_ser(index, series) # :nodoc: + def write_ser(series) # :nodoc: + index = @series_index + @series_index += 1 + @writer.tag_elements('c:ser') do # Write the c:idx element. write_idx(index) # Write the c:order element. write_order(index) @@ -1555,13 +1784,13 @@ # Write the c:marker element. write_marker(series[:_marker]) # Write the c:invertIfNegative element. write_c_invert_if_negative(series[:_invert_if_neg]) # Write the c:dLbls element. - write_d_lbls(series[:labels]) + write_d_lbls(series[:_labels]) # Write the c:trendline element. - write_trendline(series[:trendline]) + write_trendline(series[:_trendline]) # Write the c:cat element. write_cat(series) # Write the c:val element. write_val(series) end @@ -1607,18 +1836,18 @@ data = @formula_data[data_id] if data_id # Ignore <c:cat> elements for charts without category values. return unless formula - @has_category = 1 + @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 = 0 - # Write the c:numRef element. + @has_category = false + # Write the c:strRef element. write_str_ref(formula, data, type) else # Write the c:numRef element. write_num_ref(formula, data, type) end @@ -1634,27 +1863,22 @@ def write_val_base(formula, data_id, tag) # :nodoc: data = @formula_data[data_id] @writer.tag_elements(tag) do - # Check the type of cached data. - type = get_data_type(data) - if type == 'str' - # Write the c:numRef element. - write_str_ref(formula, data, type) - else - # Write the c:numRef element. - write_num_ref(formula, data, type) - end + # Unlike Cat axes data should only be numeric. + + # Write the c:numRef element. + write_num_ref(formula, data, 'num') end end # - # Write the <c:numRef> element. + # Write the <c:numRef> or <c:strRef> element. # - def write_num_ref(formula, data, type) # :nodoc: - @writer.tag_elements('c:numRef') do + def write_num_or_str_ref(tag, formula, data, type) # :nodoc: + @writer.tag_elements(tag) do # Write the c:f element. write_series_formula(formula) if type == 'num' # Write the c:numCache element. write_num_cache(data) @@ -1664,24 +1888,21 @@ end end end # + # Write the <c:numRef> element. + # + def write_num_ref(formula, data, type) # :nodoc: + write_num_or_str_ref('c:numRef', formula, data, type) + end + + # # Write the <c:strRef> element. # def write_str_ref(formula, data, type) # :nodoc: - @writer.tag_elements('c:strRef') do - # Write the c:f element. - write_series_formula(formula) - if type == 'num' - # Write the c:numCache element. - write_num_cache(data) - elsif type == 'str' - # Write the c:strCache element. - write_str_cache(data) - end - end + write_num_or_str_ref('c:strRef', formula, data, type) end # # Write the <c:f> element. # @@ -1691,10 +1912,28 @@ @writer.data_element('c:f', formula) end # + # Write the <c:axId> elements for the primary or secondary axes. + # + def write_axis_ids(params) + # Generate the axis ids. + add_axis_ids(params) + + if params[:primary_axes] != 0 + # Write the axis ids for the primary axes. + write_axis_id(@axis_ids[0]) + write_axis_id(@axis_ids[1]) + else + # Write the axis ids for the secondary axes. + write_axis_id(@axis2_ids[0]) + write_axis_id(@axis2_ids[1]) + end + end + + # # Write the <c:axId> element. # def write_axis_id(val) # :nodoc: attributes = ['val', val] @@ -1702,44 +1941,49 @@ end # # Write the <c:catAx> element. Usually the X axis. # - def write_cat_axis(position = nil) # :nodoc: + def write_cat_axis(params) # :nodoc: + x_axis = params[:x_axis] + y_axis = params[:y_axis] + axis_ids = params[:axis_ids] + + # if there are no axis_ids then we don't need to write this element + return unless axis_ids + return if axis_ids.empty? + position = @cat_axis_position horiz = @horiz_cat_axis - x_axis = @x_axis - y_axis = @y_axis # Overwrite the default axis position with a user supplied value. position = x_axis[:_position] || position @writer.tag_elements('c:catAx') do - write_axis_id(@axis_ids[0]) + write_axis_id(axis_ids[0]) # Write the c:scaling element. write_scaling(x_axis[:_reverse]) + + write_delete(1) unless ptrue?(x_axis[:_visible]) + # Write the c:axPos element. write_axis_pos(position, y_axis[:_reverse]) # Write the axis title elements. - if title = @x_axis[:_formula] + if title = x_axis[:_formula] write_title_formula(title, @x_axis[:_data_id], horiz) - elsif title = @x_axis[:_name] + elsif title = x_axis[:_name] write_title_rich(title, horiz) end # Write the c:numFmt element. write_num_fmt # Write the c:tickLblPos element. write_tick_label_pos(x_axis[:_label_position]) # Write the c:crossAx element. - write_cross_axis(@axis_ids[1]) - # Note, the category crossing comes from the value axis. - if nil_or_max?(y_axis[:_crossing]) - # Write the c:crosses element. - write_crosses(y_axis[:_crossing]) - else - # Write the c:crossesAt element. - write_c_crosses(y_axis[:_crossing]) + write_cross_axis(axis_ids[1]) + + if @show_crosses || ptrue?(x_axis[:_visible]) + write_crossing(y_axis[:_crossing]) end # Write the c:auto element. write_auto(1) # Write the c:labelAlign element. write_label_align('ctr') @@ -1751,45 +1995,120 @@ # # Write the <c:valAx> element. Usually the Y axis. # # TODO. Maybe should have a _write_cat_val_axis method as well for scatter. # - def write_val_axis(position = nil, hide_major_gridlines = nil) # :nodoc: - params = { - :axis_position => @y_axis[:_position], - :axis_id => @axis_ids[1], - :scaling_axis => @y_axis, - :axis_position_element => @x_axis[:_reverse], - :title_axis => @y_axis, - :tick_label_pos => @y_axis[:_label_position], - :cross_axis => @axis_ids[0], - :category_crossing => @x_axis[:_crossing], - :major_unit => @y_axis[:_major_unit], - :minor_unit => @y_axis[:_minor_unit] - } - write_val_axis_common(position, hide_major_gridlines, params) + def write_val_axis(params) # :nodoc: + x_axis = params[:x_axis] + y_axis = params[:y_axis] + axis_ids = params[:axis_ids] + position = params[:position] || @val_axis_position + horiz = @horiz_val_axis + + return unless axis_ids && !axis_ids.empty? + + # OVerwrite the default axis position with a user supplied value. + position = y_axis[:_position] || position + + @writer.tag_elements('c:valAx') do + write_axis_id(axis_ids[1]) + + # Write the c:scaling element. + write_scaling_with_param(y_axis) + + write_delete(1) unless ptrue?(y_axis[:_visible]) + + # Write the c:axPos element. + write_axis_pos(position, x_axis[:_reverse]) + + # 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) + elsif title = y_axis[:_name] + write_title_rich(title, horiz) + end + + # Write the c:numberFormat element. + write_number_format + + # Write the tickLblPos element. + write_tick_label_pos(y_axis[:_label_position]) + + # Write the c:crossAx element. + write_cross_axis(axis_ids[0]) + + write_crossing(x_axis[:_crossing]) + + # Write the c:crossBetween element. + write_cross_between + + # Write the c:majorUnit element. + write_c_major_unit(y_axis[:_major_unit]) + + # Write the c:minorUnit element. + write_c_minor_unit(y_axis[:_minor_unit]) + end end # # Write the <c:valAx> element. This is for the second valAx in scatter plots. # # Usually the X axis. # - def write_cat_val_axis(position, hide_major_gridlines) # :nodoc: - params = { - :axis_position => @x_axis[:_position], - :axis_id => @axis_ids[0], - :scaling_axis => @x_axis, - :axis_position_element => @y_axis[:_reverse], - :title_axis => @x_axis, - :tick_label_pos => @x_axis[:_label_position], - :cross_axis => @axis_ids[1], - :category_crossing => @y_axis[:_crossing], - :major_unit => @x_axis[:_major_unit], - :minor_unit => @x_axis[:_minor_unit] - } - write_val_axis_common(position, hide_major_gridlines, params) + def write_cat_val_axis(params) # :nodoc: + x_axis = params[:x_axis] + y_axis = params[:y_axis] + axis_ids = params[:axis_ids] + position = params[:position] || @val_axis_position + horiz = @horiz_val_axis + + return unless axis_ids && !axis_ids.empty? + + # Overwrite the default axis position with a user supplied value. + position = x_axis[:_position] || position + + @writer.tag_elements('c:valAx') do + write_axis_id(axis_ids[0]) + + # Write the c:scaling element. + write_scaling_with_param(x_axis) + + write_delete(1) unless ptrue?(x_axis[:_visible]) + + # 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) + elsif title = x_axis[:_name] + write_title_rich(title, horiz) + end + + # Write the c:numberFormat element. + write_number_format + + # Write the c:tickLblPos element. + write_tick_label_pos(x_axis[:_label_position]) + + # Write the c:crossAx element. + write_cross_axis(axis_ids[1]) + + write_crossing(y_axis[:_crossing]) + + # Write the c:crossBetween element. + write_cross_between + + # Write the c:majorUnit element. + write_c_major_unit(x_axis[:_major_unit]) + + # Write the c:minorunit element. + write_c_minor_unit(x_axis[:_minor_unit]) + end end def write_val_axis_common(position, hide_major_gridlines, params) # :nodoc: position ||= @val_axis_position horiz = @horiz_val_axis @@ -1798,13 +2117,12 @@ position = params[:axis_position] || position @writer.tag_elements('c:valAx') do write_axis_id(params[:axis_id]) # Write the c:scaling element. - write_scaling( - params[:scaling_axis][:_reverse], params[:scaling_axis][:_min], - params[:scaling_axis][:_max], params[:scaling_axis][:_log_base]) + write_scaling_with_param(params[:scaling_axis]) + # Write the c:axPos element. write_axis_pos(position, params[:axis_position_element]) # Write the c:majorGridlines element. write_major_gridlines unless hide_major_gridlines # Write the axis title elements. @@ -1817,18 +2135,13 @@ write_number_format # Write the c:tickLblPos element. write_tick_label_pos(params[:tick_label_pos]) # Write the c:crossAx element. write_cross_axis(params[:cross_axis]) - # Note, the category crossing comes from the value axis. - if nil_or_max?(params[:category_crossing]) - # Write the c:crosses element. - write_crosses(params[:category_crossing]) - else - # Write the c:crossesAt element. - write_c_crosses_at(params[:category_crossing]) - end + + write_crossing(params[:category_crossing]) + # Write the c:crossBetween element. write_cross_between # Write the c:majorUnit element. write_c_major_unit(params[:major_unit]) # Write the c:minorUnit element. @@ -1837,19 +2150,29 @@ end # # Write the <c:dateAx> element. Usually the X axis. # - def write_date_axis(position = nil) # :nodoc: + def write_date_axis(params) # :nodoc: + x_axis = params[:x_axis] + y_axis = params[:y_axis] + axis_ids = params[:axis_ids] + + return unless axis_ids && !axis_ids.empty? + position = @cat_axis_position - x_axis = @x_axis - y_axis = @y_axis + # Overwrite the default axis position with a user supplied value. + position = x_axis[:_position] || position + @writer.tag_elements('c:dateAx') do - write_axis_id(@axis_ids[0]) + write_axis_id(axis_ids[0]) # Write the c:scaling element. - write_scaling(x_axis[:reverse], x_axis[:_min], x_axis[:_max], x_axis[:_log_base]) + write_scaling_with_param(x_axis) + + write_delete(1) unless ptrue?(x_axis[:_visible]) + # 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]) @@ -1859,19 +2182,16 @@ # Write the c:numFmt element. write_num_fmt('dd/mm/yyyy') # Write the c:tickLblPos element. write_tick_label_pos(x_axis[:_label_position]) # Write the c:crossAx element. - write_cross_axis(@axis_ids[1]) - # Note, the category crossing comes from the value axis. - if nil_or_max?(y_axis[:_crossing]) - # Write the c:crossing element. - write_crosses(y_axis[:_crossing]) - else - # Write the c:crossesAt element. - write_c_crosses_at(y_axis[:_crossing]) + write_cross_axis(axis_ids[1]) + + if @show_crosses || ptrue?(x_axis[:_visible]) + write_crossing(y_axis[:_crossing]) end + # Write the c:auto element. write_auto(1) # Write the c:labelOffset element. write_label_offset(100) # Write the c:majorUnit element. @@ -1887,10 +2207,29 @@ write_c_minor_time_unit(x_axis[:_minor_unit_type]) end end end + def write_crossing(crossing) + # Note, the category crossing comes from the value axis. + if nil_or_max?(crossing) + # Write the c:crosses element. + write_crosses(crossing) + else + # Write the c:crossesAt element. + write_c_crosses_at(crossing) + end + end + + def write_scaling_with_param(param) + write_scaling( + param[:_reverse], + param[:_min], + param[:_max], + param[:_log_base] + ) + end # # Write the <c:scaling> element. # def write_scaling(reverse, min = nil, max = nil, log_base = nil) # :nodoc: @writer.tag_elements('c:scaling') do @@ -1904,36 +2243,25 @@ write_c_min(min) end end # - # Write the <c:orientation> element. - # - def write_orientation(reverse = nil) # :nodoc: - val = reverse ? 'maxMin' : 'minMax' - - attributes = ['val', val] - - @writer.empty_tag('c:orientation', attributes) - end - - # # Write the <c:logBase> element. # def write_c_log_base(val) # :nodoc: - return if val == 0 || val.nil? + return unless ptrue?(val) attributes = ['val', val] @writer.empty_tag('c:logBase', attributes) end # # Write the <c:orientation> element. # - def write_orientation(reverse = 'maxMin') # :nodoc: - val = 'minMax' + def write_orientation(reverse = nil) # :nodoc: + val = ptrue?(reverse) ? 'maxMin' : 'minMax' attributes = ['val', val] @writer.empty_tag('c:orientation', attributes) end @@ -2061,11 +2389,13 @@ end # # Write the <c:majorGridlines> element. # - def write_major_gridlines # :nodoc: + def write_major_gridlines(options = {}) # :nodoc: + return unless ptrue?(options[:visible]) + @writer.empty_tag('c:majorGridlines') end # # Write the <c:numberFormat> element. @@ -2118,20 +2448,22 @@ end # # Write the <c:majorTimeUnit> element. # - def write_c_major_time_unit(val = 'days') # :nodoc: + def write_c_major_time_unit(val) # :nodoc: + val ||= 'days' attributes = ['val', val] @writer.empty_tag('c:majorTimeUnit', attributes) end # # Write the <c:minorTimeUnit> element. # - def write_c_minor_time_unit(val = 'days') # :nodoc: + def write_c_minor_time_unit(val) # :nodoc: + val ||= 'days' attributes = ['val', val] @writer.empty_tag('c:minorTimeUnit', attributes) end @@ -2214,10 +2546,13 @@ # Write the <c:plotVisOnly> element. # def write_plot_vis_only # :nodoc: val = 1 + # Ignore this element if we are plitting hidden data. + return if @show_hidden_data + attributes = ['val', val] @writer.empty_tag('c:plotVisOnly', attributes) end @@ -2285,11 +2620,11 @@ end # # Write the <c:title> element for a rich string. # - def write_title_formula(title, data_id, horiz) # :nodoc: + def write_title_formula(title, data_id, horiz = 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 @@ -2345,11 +2680,11 @@ attributes = [ 'rot', rot, 'vert', vert ] - attributes = [] if !horiz || horiz == 0 + attributes = [] unless ptrue?(horiz) @writer.empty_tag('a:bodyPr', attributes) end # @@ -2372,11 +2707,11 @@ end # # Write the <a:p> element for formula titles. # - def write_a_p_formula(title) # :nodoc: + def write_a_p_formula # :nodoc: @writer.tag_elements('a:p') do # Write the a:pPr element. write_a_p_pr_formula # Write the a:endParaRPr element. write_a_end_para_rpr @@ -2463,19 +2798,19 @@ # Write the <c:marker> element. # def write_marker(marker = nil) # :nodoc: marker ||= @default_marker - return if marker.nil? || marker == 0 - return if marker[:automatic] && marker[:automatic] != 0 + return unless ptrue?(marker) + return if ptrue?(marker[:automatic]) @writer.tag_elements('c:marker') do # Write the c:symbol element. write_symbol(marker[:type]) # Write the c:size element. size = marker[:size] - write_marker_size(size) if !size.nil? && size != 0 + write_marker_size(size) if ptrue?(size) # Write the c:spPr element. write_sp_pr(marker) end end @@ -2512,18 +2847,26 @@ # # Write the <c:spPr> element. # def write_sp_pr(series) # :nodoc: - return if (!series.has_key?(:_line) || series[:_line][:_defined].nil? || series[:_line][:_defined]== 0) && - (!series.has_key?(:_fill) || series[:_fill][:_defined].nil? || series[:_fill][:_defined]== 0) + return if (!series.has_key?(:_line) || !ptrue?(series[:_line][:_defined])) && + (!series.has_key?(:_fill) || !ptrue?(series[:_fill][:_defined])) @writer.tag_elements('c:spPr') do - # Write the a:solidFill element for solid charts such as pie and bar. - write_a_solid_fill(series[:_fill]) if series[:_fill] && series[:_fill][:_defined] != 0 + # Write the fill elements for solid charts such as pie and bar. + if series[:_fill] && series[:_fill][:_defined] != 0 + if ptrue?(series[:_fill][:none]) + # Write the a:noFill element. + write_a_no_fill + else + # Write the a:solidFill element. + write_a_solid_fill(series[:_fill]) + end + end # Write the a:ln element. - write_a_ln(series[:_line]) if series[:_line] && series[:_line][:_defined] + write_a_ln(series[:_line]) if series[:_line] && series[:_line][:_defined] != 0 end end # # Write the <a:ln> element. @@ -2542,11 +2885,11 @@ attributes = ['w', width] end @writer.tag_elements('a:ln', attributes) do # Write the line fill. - if !line[:none].nil? && line[:none] != 0 + if ptrue?(line[:none]) # Write the a:noFill element. write_a_no_fill else # Write the a:solidFill element. write_a_solid_fill(line) @@ -2604,23 +2947,23 @@ def write_trendline(trendline) # :nodoc: return unless trendline @writer.tag_elements('c:trendline') do # Write the c:name element. - write_name(trendline[name]) + write_name(trendline[:name]) # Write the c:spPr element. write_sp_pr(trendline) # Write the c:trendlineType element. - write_trendline_type(trendline[type]) + write_trendline_type(trendline[:type]) # Write the c:order element for polynomial trendlines. - write_trendline_order(trendline[order]) if trendline[type] == 'poly' + write_trendline_order(trendline[:order]) if trendline[:type] == 'poly' # Write the c:period element for moving average trendlines. - write_period(trendline[period]) if trendline[type] == 'movingAvg' + write_period(trendline[:period]) if trendline[:type] == 'movingAvg' # Write the c:forward element. - write_forward(trendline[forward]) + write_forward(trendline[:forward]) # Write the c:backward element. - write_backward(trendline[backward]) + write_backward(trendline[:backward]) end end # # Write the <c:trendlineType> element. @@ -2701,13 +3044,29 @@ # # Write the <c:numCache> element. # def write_num_cache(data) # :nodoc: @writer.tag_elements('c:numCache') do + + # Write the c:formatCode element. write_format_code('General') + + # Write the c:ptCount element. write_pt_count(data.size) - write_pts(data) + + (0..data.size - 1).each do |i| + token = data[i] + + # Write non-numeric data as 0. + if token && + !(token.to_s =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) + token = 0 + end + + # Write the c:pt element. + write_pt(i, token) + end end end # # Write the <c:strCache> element. @@ -2771,16 +3130,22 @@ # def write_d_lbls(labels) # :nodoc: return unless labels @writer.tag_elements('c:dLbls') do + # Write the c:dLblPos element. + write_d_lbl_pos(labels[:position]) if labels[:position] # Write the c:showVal element. - write_show_val if labels[value] + write_show_val if labels[:value] # Write the c:showCatName element. - write_show_cat_name if labels[category] + write_show_cat_name if labels[:category] # Write the c:showSerName element. - write_show_ser_name if labels[series_name] + write_show_ser_name if labels[:series_name] + # Write the c:showPercent element. + write_show_percent if labels[:percentage] + # Write the c:showLeaderLines element. + write_show_leader_lines if labels[:leader_lines] end end # # Write the <c:showVal> element. @@ -2814,10 +3179,41 @@ @writer.empty_tag('c:showSerName', attributes) end # + # Write the <c:showPercent> element. + # + def write_show_percent + val = 1 + + attributes = ['val', val] + + @writer.empty_tag('c:showPercent', attributes) + end + + # + # Write the <c:showLeaderLines> element. + # + def write_show_leader_lines + val = 1 + + attributes = ['val', val] + + @writer.empty_tag('c:showLeaderLines', attributes) + end + + # + # Write the <c:dLblPos> element. + # + def write_d_lbl_pos(val) + attributes = ['val', val] + + @writer.empty_tag('c:dLblPos', attributes) + end + + # # Write the <c:delete> element. # def write_delete(val) # :nodoc: attributes = ['val', val] @@ -2828,10 +3224,10 @@ # Write the <c:invertIfNegative> element. # def write_c_invert_if_negative(invert = nil) # :nodoc: val = 1 - return unless invert + return unless invert && invert != 0 attributes = ['val', val] @writer.empty_tag('c:invertIfNegative', attributes) end