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