diff --git a/examples/example.rb b/examples/example.rb
index 116ce730..865d248b 100755
--- a/examples/example.rb
+++ b/examples/example.rb
@@ -50,6 +50,7 @@
examples << :cached_formula
examples << :page_breaks
examples << :rich_text
+examples << :multi_chart
p = Axlsx::Package.new
wb = p.workbook
@@ -659,7 +660,7 @@
## Book Views
#
-## Book views let you specify which sheet the show as active when the user opens the work book as well as a bunch of other
+## Book views let you specify which sheet the show as active when the user opens the work book as well as a bunch of other
## tuning values for the UI @see Axlsx::WorkbookView
## ```ruby
if examples.include? :book_view
@@ -825,4 +826,32 @@
end
p.serialize 'rich_text.xlsx'
end
-#```
\ No newline at end of file
+#```
+
+#```ruby
+if examples.include? :multi_chart
+ p = Axlsx::Package.new
+ wb = p.workbook
+ wb.add_worksheet(:name => "Line Chart") do |sheet|
+ sheet.add_row ["Simple Line Chart"]
+ sheet.add_row %w(first second)
+ 4.times do
+ sheet.add_row [ rand(24)+1, rand(24)+1]
+ end
+ sheet.add_chart(Axlsx::MultiChart) do |mchart|
+ mchart.add_sub_chart(Axlsx::BarChart, :title => "Simple 3D Bar Chart", :rotX => 30, :rotY => 20, :barDir => :col) do |chart|
+ chart.start_at 0, 5
+ chart.end_at 10, 20
+ chart.add_series :data => sheet["A3:A6"], :title => sheet["A2"], :color => "0000FF"
+ chart.catAxis.title = 'X Axis'
+ chart.valAxis.title = 'Y Axis'
+ end
+ mchart.add_sub_chart(Axlsx::LineChart, :title => "Simple Line Chart", :rotX => 30, :rotY => 20) do |chart|
+ chart.add_series :data => sheet["A3:A6"], :title => sheet["A2"], :labels => [], :color => "FF0000", :show_marker => true, :smooth => true
+ end
+
+ end
+ end
+ p.serialize 'multi_chart.xlsx'
+end
+#```
diff --git a/lib/axlsx/drawing/bar_chart.rb b/lib/axlsx/drawing/bar_chart.rb
new file mode 100644
index 00000000..7afcbd1e
--- /dev/null
+++ b/lib/axlsx/drawing/bar_chart.rb
@@ -0,0 +1,150 @@
+# encoding: UTF-8
+module Axlsx
+
+ # The BarChart is a barchart (who would have guessed?) that you can add to your worksheet.
+ # @see Worksheet#add_chart
+ # @see Chart#add_series
+ # @see Package#serialize
+ # @see README for an example
+ class BarChart < Chart
+
+ # the category axis
+ # @return [CatAxis]
+ def cat_axis
+ axes[:cat_axis]
+ end
+ alias :catAxis :cat_axis
+
+ # the value axis
+ # @return [ValAxis]
+ def val_axis
+ axes[:val_axis]
+ end
+ alias :valAxis :val_axis
+
+ # The direction of the bars in the chart
+ # must be one of [:bar, :col]
+ # @return [Symbol]
+ def bar_dir
+ @bar_dir ||= :bar
+ end
+ alias :barDir :bar_dir
+
+ # space between bar or column clusters, as a percentage of the bar or column width.
+ # @return [String]
+ attr_reader :gap_depth
+ alias :gapDepth :gap_depth
+
+ # space between bar or column clusters, as a percentage of the bar or column width.
+ # @return [String]
+ def gap_width
+ @gap_width ||= 150
+ end
+ alias :gapWidth :gap_width
+
+ #grouping for a column, line, or area chart.
+ # must be one of [:percentStacked, :clustered, :standard, :stacked]
+ # @return [Symbol]
+ def grouping
+ @grouping ||= :clustered
+ end
+
+ # The shabe of the bars or columns
+ # must be one of [:cone, :coneToMax, :box, :cylinder, :pyramid, :pyramidToMax]
+ # @return [Symbol]
+ def shape
+ @shape ||= :box
+ end
+
+ # validation regex for gap amount percent
+ GAP_AMOUNT_PERCENT = /0*(([0-9])|([1-9][0-9])|([1-4][0-9][0-9])|500)%/
+
+ # Creates a new bar chart object
+ # @param [GraphicFrame] frame The workbook that owns this chart.
+ # @option options [Cell, String] title
+ # @option options [Boolean] show_legend
+ # @option options [Symbol] bar_dir
+ # @option options [Symbol] grouping
+ # @option options [String] gap_width
+ # @option options [String] gap_depth
+ # @option options [Symbol] shape
+ # @option options [Integer] rot_x
+ # @option options [String] h_percent
+ # @option options [Integer] rot_y
+ # @option options [String] depth_percent
+ # @option options [Boolean] r_ang_ax
+ # @option options [Integer] perspective
+ # @see Chart
+ # @see View3D
+ def initialize(frame, options={})
+ @vary_colors = true
+ @gap_width, @gap_depth, @shape = nil, nil, nil
+ super(frame, options)
+ @series_type = BarSeries
+ @d_lbls = nil
+ end
+
+ # The direction of the bars in the chart
+ # must be one of [:bar, :col]
+ def bar_dir=(v)
+ RestrictionValidator.validate "BarChart.bar_dir", [:bar, :col], v
+ @bar_dir = v
+ end
+ alias :barDir= :bar_dir=
+
+ #grouping for a column, line, or area chart.
+ # must be one of [:percentStacked, :clustered, :standard, :stacked]
+ def grouping=(v)
+ RestrictionValidator.validate "BarChart.grouping", [:percentStacked, :clustered, :standard, :stacked], v
+ @grouping = v
+ end
+
+ # space between bar or column clusters, as a percentage of the bar or column width.
+ def gap_width=(v)
+ RegexValidator.validate "BarChart.gap_width", GAP_AMOUNT_PERCENT, v
+ @gap_width=(v)
+ end
+ alias :gapWidth= :gap_width=
+
+ # space between bar or column clusters, as a percentage of the bar or column width.
+ def gap_depth=(v)
+ RegexValidator.validate "BarChart.gap_didth", GAP_AMOUNT_PERCENT, v
+ @gap_depth=(v)
+ end
+ alias :gapDepth= :gap_depth=
+
+ # The shabe of the bars or columns
+ # must be one of [:cone, :coneToMax, :box, :cylinder, :pyramid, :pyramidToMax]
+ def shape=(v)
+ RestrictionValidator.validate "BarChart.shape", [:cone, :coneToMax, :box, :cylinder, :pyramid, :pyramidToMax], v
+ @shape = v
+ end
+
+ # Serializes the object
+ # @param [String] str
+ # @return [String]
+ def to_xml_string(str = '')
+ super(str) do
+ str << ''
+ str << ('')
+ str << ('')
+ str << ('')
+ @series.each { |ser| ser.to_xml_string(str) }
+ @d_lbls.to_xml_string(str) if @d_lbls
+ str << ('') unless @gap_width.nil?
+ str << ('') unless @gap_depth.nil?
+ str << ('') unless @shape.nil?
+ axes.to_xml_string(str, :ids => true)
+ str << ''
+ axes.to_xml_string(str)
+ end
+ end
+
+ # A hash of axes used by this chart. Bar charts have a value and
+ # category axes specified via axes[:val_axes] and axes[:cat_axis]
+ # @return [Axes]
+ def axes
+ @axes ||= Axes.new(:cat_axis => CatAxis, :val_axis => ValAxis)
+ end
+ end
+end
diff --git a/lib/axlsx/drawing/chart.rb b/lib/axlsx/drawing/chart.rb
index 1d6b9293..74a07148 100644
--- a/lib/axlsx/drawing/chart.rb
+++ b/lib/axlsx/drawing/chart.rb
@@ -23,6 +23,7 @@ def initialize(frame, options={})
@display_blanks_as = :gap
@series_type = Series
@title = Title.new
+ @d_table = nil
parse_options options
start_at(*options[:start_at]) if options[:start_at]
end_at(*options[:end_at]) if options[:end_at]
@@ -33,6 +34,8 @@ def initialize(frame, options={})
attr_reader :view_3D
alias :view3D :view_3D
+ attr_reader :d_table
+
# A reference to the graphic frame that owns this chart
# @return [GraphicFrame]
attr_reader :graphic_frame
@@ -53,7 +56,7 @@ def d_lbls
# Indicates that colors should be varied by datum
# @return [Boolean]
attr_reader :vary_colors
-
+
# Configures the vary_colors options for this chart
# @param [Boolean] v The value to set
def vary_colors=(v) Axlsx::validate_boolean(v); @vary_colors = v; end
@@ -164,6 +167,7 @@ def to_xml_string(str = '')
str << ''
str << ''
yield if block_given?
+ @d_table.to_xml_string(str) if @d_table
str << ''
if @show_legend
str << ''
@@ -227,6 +231,7 @@ def end_at(x=10, y=10)
def view_3D=(v) DataTypeValidator.validate "#{self.class}.view_3D", View3D, v; @view_3D = v; end
alias :view3D= :view_3D=
+ def d_table=(v) DataTypeValidator.validate "#{self.class}.d_table", DTable, v; @d_table = v; end
end
end
diff --git a/lib/axlsx/drawing/d_table.rb b/lib/axlsx/drawing/d_table.rb
new file mode 100644
index 00000000..ec58f7df
--- /dev/null
+++ b/lib/axlsx/drawing/d_table.rb
@@ -0,0 +1,62 @@
+module Axlsx
+ # There are more elements in the dTable spec that allow for
+ # customizations and formatting. For now, I am just implementing the
+ # basics.
+ class DTable
+
+ include Axlsx::Accessors
+ include Axlsx::OptionsParser
+ # creates a new DTable object
+ def initialize(chart_type, options={})
+ raise ArgumentError, 'chart_type must inherit from Chart' unless [Chart, LineChart].include?(chart_type.superclass)
+ @chart_type = chart_type
+ initialize_defaults
+ parse_options options
+ end
+
+ # These attributes are all boolean so I'm doing a bit of a hand
+ # waving magic show to set up the attriubte accessors
+ # @note
+ # not all charts support all methods!
+ #
+ boolean_attr_accessor :show_horz_border,
+ :show_vert_border,
+ :show_outline,
+ :show_keys
+
+ # Initialize all the values to false as Excel requires them to
+ # explicitly be disabled or all will show.
+ def initialize_defaults
+ [:show_horz_border, :show_vert_border,
+ :show_outline, :show_keys].each do |attr|
+ self.send("#{attr}=", false)
+ end
+ end
+
+ # The chart type that is using this data table instance.
+ # This affects the xml output as not all chart types support the
+ # same data table attributes.
+ attr_reader :chart_type
+
+ # serializes the data labels
+ # @return [String]
+ def to_xml_string(str = '')
+ # validate_attributes_for_chart_type
+ str << ''
+ %w(show_horz_border show_vert_border show_outline show_keys).each do |key|
+ next unless instance_values.keys.include?(key) && instance_values[key] != nil
+ str << ""
+ end
+ str << ''
+ end
+
+ # nills out d_lbl_pos and show_leader_lines as these attributes, while valid in the spec actually chrash excel for any chart type other than pie charts.
+ # def validate_attributes_for_chart_type
+ # return if @chart_type == Pie3DChart
+ # @d_lbl_pos = nil
+ # @show_leader_lines = nil
+ # end
+
+
+ end
+end
diff --git a/lib/axlsx/drawing/drawing.rb b/lib/axlsx/drawing/drawing.rb
index 2b593606..5031d9bc 100644
--- a/lib/axlsx/drawing/drawing.rb
+++ b/lib/axlsx/drawing/drawing.rb
@@ -1,6 +1,7 @@
# encoding: UTF-8
module Axlsx
require 'axlsx/drawing/d_lbls.rb'
+ require 'axlsx/drawing/d_table.rb'
require 'axlsx/drawing/title.rb'
require 'axlsx/drawing/series_title.rb'
require 'axlsx/drawing/series.rb'
@@ -33,7 +34,9 @@ module Axlsx
require 'axlsx/drawing/view_3D.rb'
require 'axlsx/drawing/chart.rb'
+ require 'axlsx/drawing/multi_chart.rb'
require 'axlsx/drawing/pie_3D_chart.rb'
+ require 'axlsx/drawing/bar_chart.rb'
require 'axlsx/drawing/bar_3D_chart.rb'
require 'axlsx/drawing/line_chart.rb'
require 'axlsx/drawing/line_3D_chart.rb'
diff --git a/lib/axlsx/drawing/multi_chart.rb b/lib/axlsx/drawing/multi_chart.rb
new file mode 100644
index 00000000..aa68521a
--- /dev/null
+++ b/lib/axlsx/drawing/multi_chart.rb
@@ -0,0 +1,65 @@
+# encoding: UTF-8
+module Axlsx
+
+ # The MultiChart is a wrapper for multiple charts that you can add to your worksheet.
+ # @see Worksheet#add_chart
+ # @see Chart#add_series
+ # @see Package#serialize
+ # @see README for an example
+ class MultiChart < Chart
+
+ module OverrideToXmlString
+ def to_xml_string(str = '')
+ yield if block_given?
+ str
+ end
+ end
+
+ # the charts this multi chart contains
+ # @return [SimpleTypedList]
+ def sub_charts
+ @sub_charts
+ end
+
+ # Creates a new multi chart object
+ # @param [GraphicFrame] frame The workbook that owns this chart.
+ # @see Chart
+ def initialize(frame, options = {})
+ super(frame, options)
+ @sub_charts ||= SimpleTypedList.new(Chart)
+ @d_table = DTable.new(self.class)
+ end
+
+ # Add a sub_chart
+ # @see Worksheet.add_chart
+ def add_sub_chart(chart_type, options = {})
+ chart = chart_type.new(@graphic_frame, options)
+ @graphic_frame.anchor.drawing.worksheet.workbook.charts.pop
+ yield chart if block_given?
+ @sub_charts << chart
+ chart
+ end
+
+ # Serializes the object
+ # @param [String] str
+ # @return [String]
+ def to_xml_string(str = '')
+ base_index = 0
+ super(str) do
+ sub_charts.each do |sub_chart|
+ # Yes, I went there
+ sub_chart.instance_eval{ class << self; self; end }.superclass.send(:include, OverrideToXmlString)
+ # Yes, I really went there
+ sub_chart.series.each do |ser|
+ ser.define_singleton_method(:index) do
+ base_index
+ end
+ base_index += 1
+ end
+ sub_chart.to_xml_string(str)
+ end
+ end
+ str
+ end
+ end
+end