Class: RVGP::Dashboard

Inherits:
Object
  • Object
show all
Defined in:
lib/rvgp/dashboard.rb

Overview

This class implements a basic graphical dashboard, for use on ansi terminals. These dashboards resemble tables, with stylized headers and footers. Here’s a rough example, of what these dashboards look like: “‘ ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ Personal Dashboard ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ ├────────────────────────────────────────────────────┬─────────────┬─────────────┬─────────────┬─────────────┤ │ Account │ 01-23 │ 02-23 │ 03-23 │ 04-23 │ ├────────────────────────────────────────────────────┼─────────────┼─────────────┼─────────────┼─────────────┤ │ Personal:Expenses:Food:Groceries │ $ 500.00 │ $ 510.00 │ $ 520.00 │ $ 530.00 │ │ Personal:Expenses:Food:Restaurants │ $ 250.00 │ $ 260.00 │ $ 270.00 │ $ 280.00 │ │ Personal:Expenses:Phone:Service │ $ 75.00 │ $ 75.00 │ $ 75.00 │ $ 75.00 │ │ Personal:Expenses:Transportation:Gas │ $ 150.00 │ $ 180.00 │ $ 280.00 │ $ 175.00 │ ├────────────────────────────────────────────────────┼─────────────┼─────────────┼─────────────┼─────────────┤ │ Expenses │ $ 975.00 │ $ 1,025.00 │ $ 1,145.00 │ $ 1,060.00 │ │ Income │ $ -2,500.00 │ $ -2,500.00 │ $ -2,500.00 │ $ -2,500.00 │ ├────────────────────────────────────────────────────┼─────────────┼─────────────┼─────────────┼─────────────┤ │ Cash Flow │ $ -1,525.00 │ $ -1,475.00 │ $ -1,355.00 │ $ -1,440.00 │ └────────────────────────────────────────────────────┴─────────────┴─────────────┴─────────────┴─────────────┘ “`

There’s a lot of functionality here, but, it’s mostly unused at the moment, outside the cashflow command. Ultimately, this is probably going to end up becoming a Grid viewing tool on the cli.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(label, csv, options = {}) ⇒ Dashboard

Create a Dashboard, which can thereafter be output to the console via #to_s

Parameters:

  • label (String)

    See #label

  • csv (String)

    See #csv

  • options (Hash) (defaults to: {})

    Additional, optional, parameters

Options Hash (options):

  • series_column_label (String)
  • pastel (Pastel) — default: Pastel.new

    A Pastel object to use, for coloring and boldfacing

  • columns_ordered_by (Proc<Array<Object>, Array<Object>>)

    This proc is sent to Enumerable#sort with two parameters, of type Array. Each of these array’s is a column. Your columns are ordered based on whether -1, 0, or 1 is returned. See Enumerable#sort for details on how this works.

  • format_data_cell (Proc<Object>)

    This proc is called, with the contents of each data cell in the dashboard. Whatever it returns is converted to a string, and rendered.

  • format_series_label (Proc<Object>)

    This proc is called, with the contents of each series label in the dashboard. Whatever it returns is converted to a string, and rendered.

  • summaries (Array<Hash<Symbol,String>>)

    An array of Hashes, each of which is expected to contain :label and :contents parameters. In addition, a :prettify parameter is also supported. Each of these Hashes are rendered at the bottom of the table, using the :label and :contents provided. If :prettify is provided, this parameter is provided the row, before rendering, so that ansi formatting is applied to the :contents.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/rvgp/dashboard.rb', line 66

def initialize(label, csv, options = {})
  @label = label
  @csv = csv
  @pastel = options[:pastel] || Pastel.new
  @series_column_label = options[:series_column_label] || 'Series'

  @columns_ordered_by = options[:columns_ordered_by]

  @format_data_cell = options[:format_data_cell]
  @format_series_label = options[:format_series_label]
  @summaries = options[:summaries]

  unless @summaries.all? { |s| %w[label contents].all? { |k| s.key? k.to_sym } }
    raise StandardError, 'One or more summaries are incomplete'
  end
end

Instance Attribute Details

#csvRVGP::Utilities::GridQuery (readonly)

The grid and data that this Dashboard will output

Returns:



34
35
36
# File 'lib/rvgp/dashboard.rb', line 34

def csv
  @csv
end

#labelString (readonly)

The label for this dashboard. This is used in the first row, of the output

Returns:

  • (String)

    the current value of label



34
35
36
# File 'lib/rvgp/dashboard.rb', line 34

def label
  @label
end

#series_column_labelString (readonly)

The label, describing what our series represents. This is also known as the ‘keystone’.

Returns:

  • (String)

    the current value of series_column_label



34
35
36
# File 'lib/rvgp/dashboard.rb', line 34

def series_column_label
  @series_column_label
end

Class Method Details

.table_width_given_column_widths(column_widths) ⇒ Integer

This helper is provided with the intention of being used with #column_data_widths. Given the return value of #column_data_widths, this method will return the width of a rendered dashboard onto the console. That means we account for padding and cell separation character(s) in this calculation. calculate

Parameters:

  • column_widths (Array<Integer>)

    The widths of each column in the table whose width you wish to

Returns:

  • (Integer)

    The width of the table, once rendered



148
149
150
151
152
153
# File 'lib/rvgp/dashboard.rb', line 148

def self.table_width_given_column_widths(column_widths)
  accumulated_width = 1 # One is the width of the left-most border '|'
  accumulated_width + column_widths.map do |w|
    [Dashboard::CELL_PADDING[1], w, Dashboard::CELL_PADDING[3], 1] # This one is the cell's right-most border '|'
  end.flatten.sum
end

Instance Method Details

#column_data_widthsArray<Integer>

Calculates the width requirements of each column, given the data that is present in that column Note that we’re not including the padding in this calculation.

Returns:

  • (Array<Integer>)

    The widths for each column



86
87
88
89
90
91
92
93
94
# File 'lib/rvgp/dashboard.rb', line 86

def column_data_widths
  # Now compute the width of each cell's contents:
  to_a.inject([]) do |ret, row|
    row.map.with_index do |cell, col_i|
      cell_width = cell.respond_to?(:length) ? cell.length : 0
      ret[col_i].nil? || ret[col_i] < cell_width ? cell_width : ret[col_i]
    end
  end
end

#to_aArray<Array<Object>>

The goal here is to return the full table, without ansi decorators, and without any to_s output options that will mutate state. The returned object’s may or may not be String, depending on whether the :format_series_row was provided to #initialize

Returns:

  • (Array<Array<Object>>)

    The grid, that this dashboard will render



100
101
102
103
104
105
106
# File 'lib/rvgp/dashboard.rb', line 100

def to_a
  # This is the table in full, without ansi, ordering, or width modifiers.
  # More or less, this is a plain text representation, in full, of the data

  @to_a ||= [[series_column_label] + sorted_headers] +
            series_rows.dup.map!(&method(:format_series_row)) + summary_rows
end

#to_s(options = {}) ⇒ String

Render this Dashboard to a string. Presumably for printing to the console.

Parameters:

  • options (Hash) (defaults to: {})

    Optional formatting specifiers

Options Hash (options):

  • column_widths (Array<Integer>)

    Use these widths for our columns, instead of the automatically deduced widths. This parameter eventually makes it’s way down to TTY::Table’s column_widths parameter.

  • show_row (Proc<Array<Object>>)

    This proc is called with a row, as its parameter. And, if the Proc returns true, the row is displayed. (And if not, the row is hidden)

  • rows_ordered_by (Proc<Array<Object>>)

    This proc is sent to Enumerable#sort_by! with a row, as its parameter. The returned value, will be used as the sort element thereafter

Returns:

  • (String)

    Your dashboard. The finished product. Print this to STDOUT



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/rvgp/dashboard.rb', line 119

def to_s(options = {})
  column_widths = options.key?(:column_widths) ? options[:column_widths] : nil
  header_row = [series_column_label] + sorted_headers
  footer_rows = summary_rows
  content_rows = series_rows

  if column_widths
    ([header_row] + content_rows + footer_rows).each { |row| row.pop row.length - column_widths.length }
  end

  # Now let's strip the rows we no longer need to show:
  content_rows.select!(&options[:show_row]) if options.key? :show_row

  # Sort the content:
  content_rows.sort_by!(&options[:rows_ordered_by]) if options.key? :rows_ordered_by

  # Then format the series and data cells:
  content_rows.map!(&method(:format_series_row))

  prettify format('%s Dashboard', label.to_s), [header_row] + content_rows + footer_rows, column_widths
end