diff --git a/lib/axlsx/drawing/bar_chart.rb b/lib/axlsx/drawing/bar_chart.rb new file mode 100644 index 00000000..2ba4fc2f --- /dev/null +++ b/lib/axlsx/drawing/bar_chart.rb @@ -0,0 +1,96 @@ +module Axlsx + + 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 + + #grouping for a column, line, or area chart. + # must be one of [:percentStacked, :clustered, :standard, :stacked] + # @return [Symbol] + def grouping + @grouping ||= :clustered + end + + # 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 + + # validation regex for gap amount percent + GAP_AMOUNT_PERCENT = /0*(([0-9])|([1-9][0-9])|([1-4][0-9][0-9])|500)%/ + + def initialize(frame, options={}) + @gap_width = nil + super(frame, options) + @series_type = BarSeries + @d_lbls = nil + @vary_colors = true + end + + def to_xml_string(str = '') + super(str) do |str_inner| + str_inner << '' + str_inner << '' + str_inner << '' + str_inner << '' + @series.each { |ser| ser.to_xml_string(str_inner) } + @d_lbls.to_xml_string(str_inner) if @d_lbls + str_inner << '' unless @gap_width.nil? + axes.to_xml_string(str_inner, :ids => true) + str_inner << '' + axes.to_xml_string(str_inner) + end + 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= + + # 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 \ No newline at end of file diff --git a/lib/axlsx/drawing/bar_series.rb b/lib/axlsx/drawing/bar_series.rb index d85345c9..888e3da8 100644 --- a/lib/axlsx/drawing/bar_series.rb +++ b/lib/axlsx/drawing/bar_series.rb @@ -53,6 +53,7 @@ def shape=(v) # @return [String] def to_xml_string(str = '') super(str) do |str_inner| + @legend_color = @colors.first colors.each_with_index do |c, index| str_inner << '' diff --git a/lib/axlsx/drawing/chart.rb b/lib/axlsx/drawing/chart.rb index c1d408e6..488fd1c7 100644 --- a/lib/axlsx/drawing/chart.rb +++ b/lib/axlsx/drawing/chart.rb @@ -14,15 +14,16 @@ class Chart # @option options [Array|String|Cell] start_at The X, Y coordinates defining the top left corner of the chart. # @option options [Array|String|Cell] end_at The X, Y coordinates defining the bottom right corner of the chart. def initialize(frame, options={}) - @style = 18 - @view_3D = nil + @style = 18 + @view_3D = nil @graphic_frame=frame @graphic_frame.anchor.drawing.worksheet.workbook.charts << self - @series = SimpleTypedList.new Series - @show_legend = true + @series = SimpleTypedList.new Series + @show_legend = true @display_blanks_as = :gap - @series_type = Series - @title = Title.new + @series_type = Series + @title = Title.new + @bg_color = nil parse_options options start_at(*options[:start_at]) if options[:start_at] end_at(*options[:end_at]) if options[:end_at] @@ -53,10 +54,16 @@ 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 + def vary_colors=(v) + Axlsx::validate_boolean(v); @vary_colors = v; + end + + # the background color for chart + # @return [String] + attr_reader :bg_color # The title object for the chart. # @return [Title] @@ -71,6 +78,7 @@ def vary_colors=(v) Axlsx::validate_boolean(v); @vary_colors = v; end # @return [Boolean] attr_reader :show_legend + # How to display blank values # Options are # * gap: Display nothing @@ -98,6 +106,11 @@ def pn "#{CHART_PN % (index+1)}" end + # @see color + def bg_color=(v) + @bg_color = v + end + # The title object for the chart. # @param [String, Cell] v # @return [Title] @@ -113,18 +126,24 @@ def title=(v) # Show the legend in the chart # @param [Boolean] v # @return [Boolean] - def show_legend=(v) Axlsx::validate_boolean(v); @show_legend = v; end + def show_legend=(v) + Axlsx::validate_boolean(v); @show_legend = v; + end # How to display blank values # @see display_blanks_as # @param [Symbol] v # @return [Symbol] - def display_blanks_as=(v) Axlsx::validate_display_blanks_as(v); @display_blanks_as = v; end + def display_blanks_as=(v) + Axlsx::validate_display_blanks_as(v); @display_blanks_as = v; + end # The style for the chart. # see ECMA Part 1 ยง21.2.2.196 # @param [Integer] v must be between 1 and 48 - def style=(v) DataTypeValidator.validate "Chart.style", Integer, v, lambda { |arg| arg >= 1 && arg <= 48 }; @style = v; end + def style=(v) + DataTypeValidator.validate "Chart.style", Integer, v, lambda { |arg| arg >= 1 && arg <= 48 }; @style = v; + end # backwards compatibility to allow chart.to and chart.from access to anchor markers # @note This will be disconinued in version 2.0.0. Please use the end_at method @@ -146,6 +165,7 @@ def add_series(options={}) @series.last end + # Serializes the object # @param [String] str # @return [String] @@ -176,6 +196,14 @@ def to_xml_string(str = '') str << '' str << '' str << '' + # chart background + if @bg_color + str << '' + str << '' + str << '' + str << '' + str << '' + end str << '' str << '' str << '' @@ -224,7 +252,10 @@ def end_at(x=10, y=10) end # sets the view_3D object for the chart - def view_3D=(v) DataTypeValidator.validate "#{self.class}.view_3D", View3D, v; @view_3D = v; end + def view_3D=(v) + DataTypeValidator.validate "#{self.class}.view_3D", View3D, v; @view_3D = v; + end + alias :view3D= :view_3D= end diff --git a/lib/axlsx/drawing/drawing.rb b/lib/axlsx/drawing/drawing.rb index e7fcba85..349327ab 100644 --- a/lib/axlsx/drawing/drawing.rb +++ b/lib/axlsx/drawing/drawing.rb @@ -34,6 +34,7 @@ module Axlsx require 'axlsx/drawing/view_3D.rb' require 'axlsx/drawing/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/series.rb b/lib/axlsx/drawing/series.rb index c9f467d5..21502d2b 100644 --- a/lib/axlsx/drawing/series.rb +++ b/lib/axlsx/drawing/series.rb @@ -16,12 +16,20 @@ class Series # @return [SeriesTitle] attr_reader :title + # The String of rgb color to apply to chart legend + # @return [String] + attr_reader :legend_color + + #The Hash of trendline options + # @return [Hash] + attr_reader :trendline + # Creates a new series # @param [Chart] chart # @option options [Integer] order # @option options [String] title def initialize(chart, options={}) - @order = nil + @order = nil self.chart = chart @chart.series << self parse_options options @@ -40,7 +48,9 @@ def order end # @see order - def order=(v) Axlsx::validate_unsigned_int(v); @order = v; end + def order=(v) + Axlsx::validate_unsigned_int(v); @order = v; + end # @see title def title=(v) @@ -49,10 +59,36 @@ def title=(v) @title = v end + # @see trendline + def trendline=(v) + @trendline = v + end + + # set trendline_color + def trendline_color + if @trendline[:color] + @trendline[:color] + else + '00000' + end + end + + #set trenline_weight + def trendline_weight + if @trendline[:weight].to_s + @trendline[:weight].to_s + else + '1' + end + + end + private # assigns the chart for this series - def chart=(v) DataTypeValidator.validate "Series.chart", Chart, v; @chart = v; end + def chart=(v) + DataTypeValidator.validate "Series.chart", Chart, v; @chart = v; + end # Serializes the object # @param [String] str @@ -63,6 +99,29 @@ def to_xml_string(str = '') str << '' title.to_xml_string(str) unless title.nil? yield str if block_given? + #set color at legend + if @legend_color + str << '' + str << '' + str << '' + str << '' + str << '' + end + + if @trendline + str << ' ' + str << ' ' + str << ' ' + str << ' ' + str << ' ' + str << ' ' + str << ' ' + str << ' ' + str << ' ' + str << ' ' + str << ' ' + str << ' ' + end str << '' end end diff --git a/lib/axlsx/drawing/title.rb b/lib/axlsx/drawing/title.rb index 5292a177..283c2b64 100644 --- a/lib/axlsx/drawing/title.rb +++ b/lib/axlsx/drawing/title.rb @@ -43,8 +43,8 @@ def cell=(v) # @param [String] str # @return [String] def to_xml_string(str = '') - str << '' unless @text.empty? + str << '' str << '' if @cell.is_a?(Cell) str << '' @@ -68,11 +68,10 @@ def to_xml_string(str = '') str << '' end str << '' - end str << '' str << '' str << '' + end end - end end diff --git a/test/drawing/tc_bar_chart.rb b/test/drawing/tc_bar_chart.rb new file mode 100644 index 00000000..b8e47ca7 --- /dev/null +++ b/test/drawing/tc_bar_chart.rb @@ -0,0 +1,58 @@ +require 'tc_helper.rb' +require 'axlsx/drawing/bar_chart' + +class TestBarChart < Test::Unit::TestCase + + def setup + @p = Axlsx::Package.new + ws = @p.workbook.add_worksheet + @row = ws.add_row ["one", 1, Time.new] + @chart = ws.add_chart Axlsx::BarChart, :title => "fishery", :bar_dir => :col + end + + def test_initialization + assert_equal(@chart.series_type, Axlsx::BarSeries, "series type incorrect") + end + + def test_chart_type + doc = Nokogiri::XML(@chart.to_xml_string) + assert_equal(1, doc.xpath('//c:barChart').size) + end + + def test_bar_direction + assert_raise(ArgumentError, "require valid bar direction") { @chart.bar_dir = :left } + assert_nothing_raised("allow valid bar direction") { @chart.bar_dir = :col } + assert(@chart.bar_dir == :col) + end + + def test_grouping + assert_raise(ArgumentError, "require valid grouping") { @chart.grouping = :inverted } + assert_nothing_raised("allow valid grouping") { @chart.grouping = :standard } + assert(@chart.grouping == :standard) + end + + def test_gapWidth + assert_raise(ArgumentError, "require valid gap width") { @chart.gap_width = 200 } + assert_nothing_raised("allow valid gapWidth") { @chart.gap_width = "200%" } + assert(@chart.gap_width == "200%") + end + + def test_to_xml_string + schema = Nokogiri::XML::Schema(File.open(Axlsx::DRAWING_XSD)) + doc = Nokogiri::XML(@chart.to_xml_string) + errors = [] + schema.validate(doc).each do |error| + errors.push error + puts error.message + end + assert(errors.empty?, "error free validation") + end + + def test_to_xml_string_has_axes_in_correct_order + str = @chart.to_xml_string + cat_axis_position = str.index(@chart.axes[:cat_axis].id.to_s) + val_axis_position = str.index(@chart.axes[:val_axis].id.to_s) + assert(cat_axis_position < val_axis_position, "cat_axis must occur earlier than val_axis in the XML") + end + +end \ No newline at end of file diff --git a/test/drawing/tc_chart.rb b/test/drawing/tc_chart.rb index 8b69e73a..26d469ad 100644 --- a/test/drawing/tc_chart.rb +++ b/test/drawing/tc_chart.rb @@ -6,13 +6,14 @@ def setup @p = Axlsx::Package.new ws = @p.workbook.add_worksheet @row = ws.add_row ["one", 1, Time.now] - @chart = ws.add_chart Axlsx::Bar3DChart, :title => "fishery" + @chart = ws.add_chart Axlsx::Bar3DChart end def teardown end def test_initialization + @chart.title = 'fishery' assert_equal(@p.workbook.charts.last,@chart, "the chart is in the workbook") assert_equal(@chart.title.text, "fishery", "the title option has been applied") assert((@chart.series.is_a?(Axlsx::SimpleTypedList) && @chart.series.empty?), "The series is initialized and empty") @@ -27,6 +28,18 @@ def test_title assert_equal(@chart.title.cell, @row.cells.first) end + def test_hidden_title + # this code must be missing + # fishery + doc = Nokogiri::XML(@chart.to_xml_string) + assert_equal(0, doc.xpath('//a:t').size) + end + + def test_bg_color + doc = Nokogiri::XML(@chart.to_xml_string) + assert_equal(0, doc.xpath('//a:t', 'a:solidFill').size) + end + def test_to_from_marker_access assert(@chart.to.is_a?(Axlsx::Marker)) assert(@chart.from.is_a?(Axlsx::Marker)) @@ -37,7 +50,7 @@ def test_style assert_nothing_raised { @chart.style = 2 } assert_equal(@chart.style, 2) end - + def test_vary_colors assert_equal(true, @chart.vary_colors) assert_raise(ArgumentError) { @chart.vary_colors = 7 }