Building Financial Stock Scanner with Ruby on Rails and R. Part 18. Prawns but not the seafood type ones.

Remember that in the Part 1 we established that one of the requirements of our application is to be able access reports on the home network as well as on the Internet.

We have mostly covered the first half. Now let’s talk how can we access reports through the Internet.

For that we need to add prawns to our cocktail of Ruby/Rails/R/Quantmod and Javascript ingredients.

Prawn is a PDF generator for Ruby. It has a bit of a learning curve – especially for those who never dealt with PDF language. Once you get it, however, it is an extremely flexible and a very powerful tool.

Why Prawn?  My first attempt was to use wicked_pdf which is an HTML to PDF generator built on the WebKit engine. It was easy to implement and the output generated was the same as for the html version of a report (a custom list or a ASX-wide one). One problem – I was not able to find an elegant way to control  page breaks. So sometimes I’d get a header of a table (i.e. name of a company, industry) on one page, but it’s charts on another one. Ugly.

So one day I decided to revisit my solution – and ended up learning some PDF (as a language) 🙂

If you have never used Prawn before I suggest you read (or, at least, scan through) the official Prawn by Example manual – which itself (naturally) comes as a PDF document generated by the Prawn.

Okay, the first thing is we to install prawn and prawn_rails gems the usual way – add to the Gemfile, run ‘bundle install’.

Next we need to create pdf view for the lists.

app/views/lists/show.pdf.prawn:

prawn_document({:optimize_objects=>true,:compress=>true}) do |pdf|
  pdf.text "#{@list.name}", :style => :bold, :size => 16
  pdf.move_down 10
  res = Array.new
  @companies.each do |c|
    res.push(c) if image_exists?("#{c.ticker}_mma.png")
  end
  res.each_slice(3).to_a.each do |s|
    data = Array.new
    s.each do |c|
      subtable = pdf.make_table([ [{:image => "#{Rails.root}/app/assets/images/#{c.ticker}_mma.png", :scale => 0.65}, {:image => "#{Rails.root}/app/assets/images/#{c.ticker}_range.png", :scale => 0.65}] ], :cell_style => {:borders => []})
      name = c.name
      ticker = Prawn::Table::Cell::Text.new(pdf, [0,0], :content => "<b>#{c.ticker}</b>", :inline_format => true)
      name = Prawn::Table::Cell::Text.new(pdf, [0,0], :content => "#{name}", :inline_format => true)
      industry = Prawn::Table::Cell::Text.new(pdf, [0,0], :content => c.industry, :inline_format => true)
      data.push([ticker, name, industry])
      data.push([{:content => subtable, :colspan => 3}])
    end
    if data.size > 0
    pdf.table data, :cell_style => {:size => 11} do
      row(0..-1).borders = []
      cells.style do |c|
        if (c.row % 2).zero?
          c.text_color = '404040'
          c.background_color = 'cccccc'
          c.borders = [:top]
          c.border_color = '555555'
        else
          c.background_color = 'f0f0f0'
        end
        row(-1).style :borders => [:bottom], :border_color => '555555'
      end
      column(0).style :align => :left, :width => 60
      column(1).style :align => :left, :width => 300
      column(2).style :align => :right, :width => 180
    end
    end
    pdf.start_new_page
  end
  pdf.number_pages "Page <page>/<total>", { :start_count_at => 0, :page_filter => :all, :at => [pdf.bounds.right - 50, 0], :align => :right, :size => 10, :color => 'a0a0a0' }
end

Let’s walk through this code.

Line 1 – we define a new pdf document and pass it to the block argument ‘pdf’. The actual document will be generated after exiting the block.

Line 2 is printing name of the list as a title of the report. It uses default Helvetica font, size 16 in bold.

Line 3 we move the cursor down by 10 pixels

Lines 4 to 7 – we create and populate array ‘res'[ult] with only those companies that have images generated, therefore we will skip those ones where, say,  there was not enough data to draw GMMAs.

Line 8 “res.each_slice(3).to_a.each do |s|" – we will print charts for three companies on each page, therefore we slice our original array into chunks of three and we iterate through these slices.

Lines 9 to 18. Here is the trick – in order to render a table with two rows and tree columns in the first row (ticker, full company name and an industry) and two equal columns  in the second row we actually need to use nested tables. The outer table will be 3×2 one. The second row will be a colspanned cell with a nested 1×2 table. Here we do not draw the outer table yet, we just define it’s cells content and assign it to the data array.

Lines 19 and 37 – we check if there is actually any data available to draw a table.

Lines 20 to 36 – here we create the outer table and apply some styling. The cells in the first row will have darker background and the top border. The second row will have lighter background and the bottom border.

Columns in the first row will have width 60, 300 and 180 points. First two have text aligned to the left, the last one text aligned to the right.

Line 38 – we start new page – after we go through a slice in the line 8.

Line 40 defines page numbering.

That’s it.

Let’s update lists_controller.rb to include pdf in the list of available formats:

)
    @companies = @list.companies.desc(:roar)
    respond_to do |format|
      format.html
      format.json
      format.pdf
    end
  end

Restart rails server and load http://localhost:3000/lists/test.pdf. You shall see a PDF document in the browser similar to this one:

Screen Shot 2015-08-18 at 11.00.04 PM

We can now share this report by either emailing it or uploading to some cloud storage. In the next post we will create a batch job that will send these reports to Google Drive.

Advertisements
Building Financial Stock Scanner with Ruby on Rails and R. Part 18. Prawns but not the seafood type ones.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s