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