class

Tablo::Summary(T, U, V)

Inherits Reference < Object

The purpose of the Summary class is to calculate, format and display aggregated source data in a specific table.

This table is usually closely linked to the main table, but it can also be presented separately (see the omit_last_rule parameter governing this link in Tablo::Table#initialize method).

The definition of the summary table is based on the following data structures:

  • UserProc for aggregate data calculation
  • HeaderColumn for header content and formatting
  • BodyColumn for formatting calculated data columns
  • BodyRow for data placement in the table

Below, a complete example illustrates how this works in practice.

Constructors

new(table : Table(T), summary_definition : U, summary_options : V)

Summary class constructor

Mandatory parameters:

  • table: This parameter references the main table

  • summary_definition: a user defined array containing n instances of Summary::UserProc, Summary::HeaderColumn, Summary::BodyColumn and Summary::BodyRow structs)

  • summary_options: a NamedTuple of Table initializers (can be empty, as the main table initializers are included by default)

Here is a complete and functional example of Detail and Summary tables "working" together (See relevant infos on usage in structs listed above)

require "tablo"
require "colorize"
require "big"

struct BigDecimal
  include Tablo::CellType
end

struct InvoiceItem
  getter product, quantity, price

  def initialize(@product : String, @quantity : Int32?, @price : BigDecimal?)
  end
end

invoice = [
  InvoiceItem.new("Laptop", 3, BigDecimal.new(980)),
  InvoiceItem.new("Printer", 2, BigDecimal.new(154.99)),
  InvoiceItem.new("Router", 1, BigDecimal.new(99)),
  InvoiceItem.new("Switch", nil, BigDecimal.new(45)),
  InvoiceItem.new("Accessories", 5, BigDecimal.new(64.50)),
]

invoice_summary_definition = [
  Tablo::Summary::UserProc.new(
    proc: ->(tbl : Tablo::Table(InvoiceItem)) {
      total_sum = BigDecimal.new(0)
      tbl.column_data(:total).each do |tot|
        total_sum += tot.as(BigDecimal) unless tot.nil?
      end
      discount = total_sum * 0.05
      total_after_discount = total_sum - discount
      tax = total_after_discount * 0.2
      total_due = total_after_discount + tax
      {
        :total_sum            => total_sum.as(Tablo::CellType),
        :discount             => discount.as(Tablo::CellType),
        :total_after_discount => total_after_discount.as(Tablo::CellType),
        :tax                  => tax.as(Tablo::CellType),
        :total_due            => total_due.as(Tablo::CellType),
      }
    }),
  Tablo::Summary::BodyColumn.new("Price", alignment: Tablo::Justify::Right),
  Tablo::Summary::BodyColumn.new(:total, alignment: Tablo::Justify::Right,
    formatter: ->(value : Tablo::CellType) {
      value.is_a?(String) ? value : (
        value.nil? ? "" : "%.2f" % value.as(BigDecimal)
      )
    },
    styler: ->(_value : Tablo::CellType, coords : Tablo::Cell::Data::Coords, content : String) {
      case coords.row_index
      when 0, 2, 5 then content.colorize.mode(:bold).to_s
      when 1       then content.colorize.mode(:italic).to_s
      else              content
      end
    }),
  Tablo::Summary::HeaderColumn.new("Product", content: ""),
  Tablo::Summary::HeaderColumn.new("Quantity", content: ""),
  Tablo::Summary::HeaderColumn.new("Price", content: "Total Invoice",
    alignment: Tablo::Justify::Right),
  Tablo::Summary::HeaderColumn.new(:total, content: "Amounts"),

  Tablo::Summary::BodyRow.new("Price", 10, "SubTotal"),
  Tablo::Summary::BodyRow.new("Price", 20, "Discount 5%"),
  Tablo::Summary::BodyRow.new("Price", 30, "S/T after discount"),
  Tablo::Summary::BodyRow.new("Price", 40, "Tax (20%)"),
  Tablo::Summary::BodyRow.new("Price", 60, "Balance due"),

  Tablo::Summary::BodyRow.new(:total, 10, -> { Tablo::Summary.use(:total_sum) }),
  Tablo::Summary::BodyRow.new(:total, 20, -> { Tablo::Summary.use(:discount) }),
  Tablo::Summary::BodyRow.new(:total, 30, -> { Tablo::Summary.use(:total_after_discount) }),
  Tablo::Summary::BodyRow.new(:total, 40, -> { Tablo::Summary.use(:tax) }),
  Tablo::Summary::BodyRow.new(:total, 50, "========"),
  Tablo::Summary::BodyRow.new(:total, 60, -> { Tablo::Summary.use(:total_due) }),
]

table = Tablo::Table.new(invoice,
  omit_last_rule: true,
  border: Tablo::Border.new(Tablo::Border::PreSet::Fancy),
  title: Tablo::Heading.new("\nInvoice\n=======\n"),
  subtitle: Tablo::Heading.new("Details", framed: true)) do |t|
  t.add_column("Product",
    &.product)
  t.add_column("Quantity",
    body_formatter: ->(value : Tablo::CellType) {
      (value.nil? ? "N/A" : value.to_s)
    }, &.quantity)
  t.add_column("Price",
    body_formatter: ->(value : Tablo::CellType) {
      "%.2f" % value.as(BigDecimal)
    }, &.price.as(Tablo::CellType))
  t.add_column(:total, header: "Total",
    body_formatter: ->(value : Tablo::CellType) {
      value.nil? ? "" : "%.2f" % value.as(BigDecimal)
    }) { |n| n.price.nil? || n.quantity.nil? ? nil : (
    n.price.as(BigDecimal) *
      n.quantity.as(Int32)
  ).as(Tablo::CellType) }
end

table.pack
table.add_summary(invoice_summary_definition,
  title: Tablo::Heading.new("Summary", framed: true))
table.summary.as(Tablo::Table).pack
puts table
puts table.summary

A few points of note:

  • Use of the BigDecimal type (not included in Tablo by default, but made possible by reopening the BigDecimal struct and adding the include Tablo::CellType statement).

  • Joining of the summary table to the main table, with the main table's omit_last_rule parameter set to true.

  • Row numbers need not be consecutive. What's important is that their order is well defined, as they will ultimately be replaced by their index in a sorted array of row values.

  • To obtain optimal result in packing, the main table must be packed before summary table definition.

Source

Class methods

use(key)

Class method to retrieve and use results of saved calculations by key (which is of type Symbol).
(see Summary::UserProc)

For example, to populate row 1 of column :total with the result of a previous calculation identified by :total_sum:

Tablo::Summary::BodyRow.new(:total, 1, -> { Tablo::Summary.use(:total_sum) })
Source

Nested types