diff --git a/lib/daru/io/exporters/excel.rb b/lib/daru/io/exporters/excel.rb index 29d01ff..e644a76 100755 --- a/lib/daru/io/exporters/excel.rb +++ b/lib/daru/io/exporters/excel.rb @@ -8,10 +8,28 @@ class Excel < Base # Exports +Daru::DataFrame+ to an Excel Spreadsheet. # + # @note For giving formatting options as hashes to the +:data+, +:index+ or +header+ + # keyword argument(s), please have a look at the + # {http://www.rubydoc.info/gems/ruby-spreadsheet/Spreadsheet/Font Spreadsheet::Font} + # and + # {http://www.rubydoc.info/gems/ruby-spreadsheet/Spreadsheet/Format Spreadsheet::Format} + # pages. + # # @param dataframe [Daru::DataFrame] A dataframe to export # @param path [String] Path of the file where the +Daru::DataFrame+ # should be written. - # @param options [Hash] A set of options containing user-preferences + # @param header [Hash or Boolean] Defaults to true. When set to false or nil, + # headers are not written. When given a hash of formatting options, + # headers are written with the specific formatting. When set to true, + # headers are written without any formatting. + # @param data [Hash or Boolean] Defaults to true. When set to false or nil, + # data values are not written. When given a hash of formatting options, + # data values are written with the specific formatting. When set to true, + # data values are written without any formatting. + # @param index [Hash or Boolean] Defaults to true. When set to false or nil, + # index values are not written. When given a hash of formatting options, + # index values are written with the specific formatting. When set to true, + # index values are written without any formatting. # # @example Writing to an Excel file without options # df = Daru::DataFrame.new([[1,2],[3,4]], order: [:a, :b]) @@ -23,34 +41,107 @@ class Excel < Base # # Daru::IO::Exporters::Excel.new(df, "dataframe_df.xls").call # - # @todo The +opts+ parameter isn't used while creating the Excel Spreadsheet - # yet. Implementing this feature will greatly allow the user to generate a - # Spreadsheet of their choice. - def initialize(dataframe, path, **options) + # @example Writing to an Excel file with formatting options + # df = Daru::DataFrame.new([[1,2],[3,4]], order: [:a, :b]) + # + # #=> # + # # a b + # # 0 1 3 + # # 1 2 4 + # + # Daru::IO::Exporters::Excel.new(df, + # "dataframe_df.xls", + # header: { color: :red, weight: :bold }, + # index: false, + # data: { color: :blue } + # ).call + # + # @example Writing a DataFrame with Multi-Index to an Excel file + # df = Daru::DataFrame.new [[1,2],[3,4]], order: [:x, :y], index: [[:a, :b, :c], [:d, :e, :f]] + # + # #=> # + # # x y + # # a b c 1 3 + # # d e f 2 4 + # + # Daru::IO::Exporters::Excel.new(df, + # "dataframe_df.xls", + # header: { color: :red, weight: :bold }, + # index: { color: :green }, + # data: { color: :blue } + # ).call + def initialize(dataframe, path, header: true, data: true, index: true) optional_gem 'spreadsheet', '~> 1.1.1' super(dataframe) - @path = path - @options = options + @path = path + @data = data + @index = index + @header = header end - # @note - # - # The +format+ variable used in this method, has to be given - # as options by the user via the +options+ hash input. - # - # Signed off by @athityakumar on 03/06/2017 at 7:00PM def call - book = Spreadsheet::Workbook.new - sheet = book.create_worksheet + @book = Spreadsheet::Workbook.new + @sheet = @book.create_worksheet + + process_offsets + write_headers - format = Spreadsheet::Format.new color: :blue, weight: :bold + @dataframe.each_row_with_index.with_index do |(row, idx), r| + write_index(idx, r+@row_offset) + write_data(row, r+@row_offset) + end + + @book.write(@path) + end + + private + + def process_offsets + @row_offset = @header ? 1 : 0 + @col_offset = 0 unless @index + @col_offset ||= @dataframe.index.is_a?(Daru::MultiIndex) ? @dataframe.index.width : 1 + end + + def write_headers + formatting( + 0...@col_offset + @dataframe.ncols, + 0, + [' '] * @col_offset + @dataframe.vectors.map(&:to_s), + @header + ) + end + + def write_index(idx, row) + formatting( + 0...@col_offset, + row, + idx, + @index + ) + end + + def write_data(row, idx) + formatting( + @col_offset...@col_offset + @dataframe.ncols, + idx, + row, + @data + ) + end - sheet.row(0).concat(@dataframe.vectors.to_a.map(&:to_s)) # Unfreeze strings - sheet.row(0).default_format = format - @dataframe.each_row_with_index { |row, i| sheet.row(i+1).concat(row.to_a) } + def formatting(col_range, row, idx, format) + return unless format + @sheet.row(row).concat( + case idx + when Daru::Vector then idx.to_a + when Array then idx.map(&:to_s) + else [idx.to_s] + end + ) - book.write(@path) + return unless format.is_a?(Hash) + col_range.each { |col| @sheet.row(row).set_format(col, Spreadsheet::Format.new(format)) } end end end diff --git a/spec/daru/io/exporters/excel_spec.rb b/spec/daru/io/exporters/excel_spec.rb index 7a94b9f..72dc64c 100644 --- a/spec/daru/io/exporters/excel_spec.rb +++ b/spec/daru/io/exporters/excel_spec.rb @@ -1,19 +1,22 @@ RSpec.describe Daru::IO::Exporters::Excel do include_context 'exporter setup' - subject do - Daru::DataFrame.rows( - Spreadsheet.open(tempfile.path).worksheet(0).rows[1..-1].map(&:to_a), - order: Spreadsheet.open(tempfile.path).worksheet(0).rows[0].to_a - ) - end - let(:filename) { 'test_write.xls' } let(:content) { Spreadsheet.open tempfile.path } + let(:opts) { {header: {color: :blue}, data: {color: :red}, index: {color: :green}} } - before { described_class.new(df, tempfile.path, opts).call } + before { described_class.new(df, tempfile.path, **opts).call } context 'writes to excel spreadsheet' do + subject do + Daru::DataFrame.rows( + Spreadsheet.open(tempfile.path).worksheet(0).rows[1..-1].map(&:to_a), + order: Spreadsheet.open(tempfile.path).worksheet(0).rows[0].to_a + ) + end + + let(:opts) { {index: false} } + it_behaves_like 'exact daru dataframe', ncols: 4, nrows: 5, @@ -25,4 +28,36 @@ [nil, 23, 4,'a','ff'] ] end + + context 'writes to excel spreadsheet with header formatting' do + subject { Spreadsheet.open(tempfile.path).worksheet(0).rows[0].format(0).font.color } + + it { is_expected.to eq(:blue) } + end + + context 'writes to excel spreadsheet with index formatting' do + subject { Spreadsheet.open(tempfile.path).worksheet(0).rows[1].format(0).font.color } + + it { is_expected.to eq(:green) } + end + + context 'writes to excel spreadsheet with data formatting' do + subject { Spreadsheet.open(tempfile.path).worksheet(0).rows[1].format(1).font.color } + + it { is_expected.to eq(:red) } + end + + context 'writes to excel spreadsheet with multi-index' do + subject { Spreadsheet.open(tempfile.path).worksheet(0).rows } + + let(:df) do + Daru::DataFrame.new( + [[1,2],[3,4]], + order: %i[x y], + index: [%i[a b c], %i[d e f]] + ) + end + + it { is_expected.to eq([[' ', ' ', ' ', 'x', 'y'], ['a', 'b', 'c', 1, 3], ['d', 'e', 'f', 2, 4]]) } + end end