From fc994dbd4519d7263d221a6021c397973216bfe5 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 31 Jul 2014 14:57:18 -0700 Subject: [PATCH 1/3] Initial support for a chart with multiple drawings - Added basic BarChart (the non-3D version) - Added initial multi-chart support. It's a bit of a hack right now. --- examples/example.rb | 33 ++++++- lib/axlsx/drawing/bar_chart.rb | 150 +++++++++++++++++++++++++++++++ lib/axlsx/drawing/drawing.rb | 2 + lib/axlsx/drawing/multi_chart.rb | 64 +++++++++++++ 4 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 lib/axlsx/drawing/bar_chart.rb create mode 100644 lib/axlsx/drawing/multi_chart.rb diff --git a/examples/example.rb b/examples/example.rb index 116ce730..4628c9c0 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/drawing.rb b/lib/axlsx/drawing/drawing.rb index 2b593606..20b6ab8e 100644 --- a/lib/axlsx/drawing/drawing.rb +++ b/lib/axlsx/drawing/drawing.rb @@ -33,7 +33,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..77185ba9 --- /dev/null +++ b/lib/axlsx/drawing/multi_chart.rb @@ -0,0 +1,64 @@ +# 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) + 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 From 219b1309a925f35f29c19b39f0525588805a3459 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 31 Jul 2014 15:08:21 -0700 Subject: [PATCH 2/3] Include all examples again --- examples/example.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example.rb b/examples/example.rb index 4628c9c0..865d248b 100755 --- a/examples/example.rb +++ b/examples/example.rb @@ -50,7 +50,7 @@ examples << :cached_formula examples << :page_breaks examples << :rich_text -examples = [:multi_chart] +examples << :multi_chart p = Axlsx::Package.new wb = p.workbook From af4d18fb256641ee03ece64286827b496169424c Mon Sep 17 00:00:00 2001 From: Phil Date: Fri, 1 Aug 2014 12:23:39 -0700 Subject: [PATCH 3/3] Support for dTable (Data Table) on multi-chart plots --- lib/axlsx/drawing/chart.rb | 7 +++- lib/axlsx/drawing/d_table.rb | 62 ++++++++++++++++++++++++++++++++ lib/axlsx/drawing/drawing.rb | 1 + lib/axlsx/drawing/multi_chart.rb | 1 + 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 lib/axlsx/drawing/d_table.rb 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 20b6ab8e..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' diff --git a/lib/axlsx/drawing/multi_chart.rb b/lib/axlsx/drawing/multi_chart.rb index 77185ba9..aa68521a 100644 --- a/lib/axlsx/drawing/multi_chart.rb +++ b/lib/axlsx/drawing/multi_chart.rb @@ -27,6 +27,7 @@ def sub_charts def initialize(frame, options = {}) super(frame, options) @sub_charts ||= SimpleTypedList.new(Chart) + @d_table = DTable.new(self.class) end # Add a sub_chart