Skip to content

Commit

Permalink
✨ Add option to include row name when printing a table
Browse files Browse the repository at this point in the history
In this commit, only the text backend is supported.

This addresses a request mentioned in the issue #18.
  • Loading branch information
ronisbr committed Mar 8, 2020
1 parent c1eb854 commit df54734
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 13 deletions.
9 changes: 9 additions & 0 deletions docs/src/man/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ Each back-end defines its own configuration keywords that can be passed using
backend.
* `filters_row`: Filters for the rows (see the section `Filters`).
* `filters_col`: Filters for the columns (see the section `Filters`).
* `row_names`: A vector containing the row names that will be appended to the
left of the table. If it is `nothing`, then the column with the
row names will not be shown. Notice that the size of this vector
must match the number of rows in the table.
(**Default** = `nothing`)
* `row_name_alignment`: Alignment of the column with the rows name (see the
section `Alignment`).
* `row_name_column_title`: Title of the column with the row names.
(**Default** = "")

!!! note

Expand Down
57 changes: 51 additions & 6 deletions src/backends/text/print.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ function _pt_text(io, pinfo;
linebreaks::Bool = false,
noheader::Bool = false,
nosubheader::Bool = false,
row_name_crayon::Crayon = Crayon(bold = true),
row_name_header_crayon::Crayon = Crayon(bold = true),
same_column_size::Bool = false,
screen_size::Union{Nothing,Tuple{Int,Int}} = nothing,
show_row_number::Bool = false,
Expand Down Expand Up @@ -92,6 +94,21 @@ function _pt_text(io, pinfo;
end
end

# If the user wants to show the row names, then convert to string.
if show_row_names
# Escape the row name column title.
row_name_column_title_str = _str_escaped(row_name_column_title)

# Convert the row names to string.
row_names_str = _str_escaped.(sprint.(print, row_names))

# Obtain the size of the row name column.
row_name_width = max(length(row_name_column_title_str),
maximum(length.(row_names_str)))
else
row_name_width = 0
end

# Make sure that `highlighters` is always a tuple.
!(highlighters isa Tuple) && (highlighters = (highlighters,))

Expand Down Expand Up @@ -245,7 +262,8 @@ function _pt_text(io, pinfo;
tf.top_line && _draw_line!(screen, buf, tf.up_left_corner,
tf.up_intersection, tf.up_right_corner, tf.row,
border_crayon, num_printed_cols, cols_width,
show_row_number, row_number_width)
show_row_number, row_number_width,
show_row_names, row_name_width)

# Header
# ==========================================================================
Expand All @@ -269,6 +287,22 @@ function _pt_text(io, pinfo;
_p!(screen, buf, border_crayon, tf.column)
end

# If we have row name column, then print in the first line the
# column title.
if show_row_names
if i == 1
header_row_name = " " *
_str_aligned(row_name_column_title_str,
row_name_alignment,
row_name_width) * " "
_p!(screen, buf, row_name_header_crayon, header_row_name)
else
_p!(screen, buf, text_crayon, " "^(row_name_width+2))
end

_p!(screen, buf, border_crayon, tf.column)
end

for j = 1:num_printed_cols
# Index of the j-th printed column in `data`.
jc = id_cols[j]
Expand Down Expand Up @@ -303,7 +337,8 @@ function _pt_text(io, pinfo;
tf.right_intersection, tf.row,
border_crayon, num_printed_cols,
cols_width, show_row_number,
row_number_width)
row_number_width, show_row_names,
row_name_width)
end

# Data
Expand All @@ -326,6 +361,14 @@ function _pt_text(io, pinfo;
_p!(screen, buf, border_crayon, tf.column)
end

if show_row_names
row_names_i_str = " " * _str_aligned(row_names_str[i],
row_name_alignment,
row_name_width) * " "
_p!(screen, buf, row_name_crayon, row_names_i_str)
_p!(screen, buf, border_crayon, tf.column)
end

for j = 1:num_printed_cols
jc = id_cols[j]

Expand Down Expand Up @@ -359,7 +402,7 @@ function _pt_text(io, pinfo;
!printed && _p!(screen, buf, text_crayon, data_ij_str)

flp = j == num_printed_cols

if j != num_printed_cols
_p!(screen, buf, border_crayon, tf.column, flp)
else
Expand All @@ -376,15 +419,16 @@ function _pt_text(io, pinfo;
i != num_rows && i in hlines &&
_draw_line!(screen, buf, hlines_format..., border_crayon,
num_printed_cols, cols_width, show_row_number,
row_number_width)
row_number_width, show_row_names, row_name_width)

# Here we must check if the vertical size of the screen has been
# reached. Notice that we must add 4 to account for the command line,
# the continuation line, the bottom table line, and the last blank line.
if (screen.size[1] > 0) && (screen.row + 4 >= screen.size[1])
_draw_continuation_row(screen, buf, tf, text_crayon, border_crayon,
num_printed_cols, cols_width,
show_row_number, row_number_width)
show_row_number, row_number_width,
show_row_names, row_name_width)
break
end
end
Expand All @@ -396,7 +440,8 @@ function _pt_text(io, pinfo;
tf.bottom_intersection,
tf.bottom_right_corner, tf.row, border_crayon,
num_printed_cols, cols_width, show_row_number,
row_number_width)
row_number_width, show_row_names,
row_name_width)

# Print the buffer
# ==========================================================================
Expand Down
21 changes: 17 additions & 4 deletions src/backends/text/private.jl
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,16 @@ end
################################################################################

"""
_draw_continuation_row(screen, io, tf, text_crayon, border_crayon, num_printed_cols, cols_width, show_row_number, row_number_width)
_draw_continuation_row(screen, io, tf, text_crayon, border_crayon, num_printed_cols, cols_width, show_row_number, row_number_width, show_row_names, row_name_width)
Draw the continuation row when the table has filled the vertical space
available. This function prints in each column the character `⋮` centered.
"""
function _draw_continuation_row(screen, io, tf, text_crayon, border_crayon,
num_printed_cols, cols_width, show_row_number,
row_number_width)
row_number_width, show_row_names,
row_name_width)

_p!(screen, io, border_crayon, tf.column)

Expand All @@ -146,6 +147,12 @@ function _draw_continuation_row(screen, io, tf, text_crayon, border_crayon,
_p!(screen, io, border_crayon, tf.column)
end

if show_row_names
row_names_i_str = _str_aligned("", :c, row_name_width + 2)
_p!(screen, io, text_crayon, row_names_i_str)
_p!(screen, io, border_crayon, tf.column)
end

@inbounds for j = 1:num_printed_cols
data_ij_str = _str_aligned("", :c, cols_width[j] + 2)
_p!(screen, io, text_crayon, data_ij_str)
Expand All @@ -162,13 +169,14 @@ function _draw_continuation_row(screen, io, tf, text_crayon, border_crayon,
end

"""
_draw_line!(screen, io, left, intersection, right, row, border_crayon, num_cols, cols_width, show_row_number, row_number_width)
_draw_line!(screen, io, left, intersection, right, row, border_crayon, num_cols, cols_width, show_row_number, row_number_width, show_row_names, row_name_width)
Draw a vertical line in `io` using the information in `screen`.
"""
function _draw_line!(screen, io, left, intersection, right, row, border_crayon,
num_cols, cols_width, show_row_number, row_number_width)
num_cols, cols_width, show_row_number, row_number_width,
show_row_names, row_name_width)

_p!(screen, io, border_crayon, left)

Expand All @@ -177,6 +185,11 @@ function _draw_line!(screen, io, left, intersection, right, row, border_crayon,
_p!(screen, io, border_crayon, intersection)
end

if show_row_names
_p!(screen, io, border_crayon, row^(row_name_width+2))
_p!(screen, io, border_crayon, intersection)
end

@inbounds for i = 1:num_cols
# Check the alignment and print.
_p!(screen, io, border_crayon, row^(cols_width[i]+2)) && break
Expand Down
25 changes: 24 additions & 1 deletion src/print.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ it is not compliant, then only the following types are supported:
`Backend`).
* `filters_row`: Filters for the rows (see the section `Filters`).
* `filters_col`: Filters for the columns (see the section `Filters`).
* `row_names`: A vector containing the row names that will be appended to the
left of the table. If it is `nothing`, then the column with the
row names will not be shown. Notice that the size of this vector
must match the number of rows in the table.
(**Default** = `nothing`)
* `row_name_alignment`: Alignment of the column with the rows name (see the
section `Alignment`).
* `row_name_column_title`: Title of the column with the row names.
(**Default** = "")
!!! note
Expand Down Expand Up @@ -520,6 +529,9 @@ function _pretty_table(io, data, header;
backend::Union{Nothing,Symbol} = nothing,
filters_row::Union{Nothing,Tuple} = nothing,
filters_col::Union{Nothing,Tuple} = nothing,
row_names::Union{Nothing,AbstractVector} = nothing,
row_name_alignment::Symbol = :r,
row_name_column_title::AbstractString = "",
kwargs...)

# Try to automatically infer the backend based on the table format type.
Expand Down Expand Up @@ -581,6 +593,16 @@ function _pretty_table(io, data, header;
length(alignment) != num_cols && error("The length of `alignment` must be the same as the number of rows.")
end

# If there is a vector of row names, then it must have the same size of the
# number of rows.
if row_names != nothing
length(row_names) != num_rows &&
error("The number of lines in `row_names` must match the number of lines in the matrix.")
show_row_names = true
else
show_row_names = false
end

# If the user wants to filter the data, then check which columns and rows
# must be printed. Notice that if a data is filtered, then it means that it
# passed the filter and must be printed.
Expand Down Expand Up @@ -627,7 +649,8 @@ function _pretty_table(io, data, header;
# Create the structure that stores the print information.
pinfo = PrintInfo(data, header, id_cols, id_rows, num_rows, num_cols,
num_printed_cols, num_printed_rows, header_num_rows,
header_num_cols, alignment)
header_num_cols, show_row_names, row_names,
row_name_alignment, row_name_column_title, alignment)

if backend == :text
_pt_text(io, pinfo; kwargs...)
Expand Down
8 changes: 6 additions & 2 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

"""
PrintInfo{Td,Th}
PrintInfo{Td,Th,Trn}
This structure stores the information required so that the backends can print
the tables.
"""
@with_kw struct PrintInfo{Td,Th}
@with_kw struct PrintInfo{Td,Th,Trn}
data::Td
header::Th
id_cols::Vector{Int}
Expand All @@ -24,5 +24,9 @@ the tables.
num_printed_rows::Int
header_num_rows::Int
header_num_cols::Int
show_row_names::Bool
row_names::Trn
row_name_alignment::Symbol
row_name_column_title::String
alignment::Vector{Symbol}
end
87 changes: 87 additions & 0 deletions test/text_backend.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,93 @@ end
@test result == expected
end

# Row names
# ==============================================================================

@testset "Show row names" begin
expected = """
┌───────┬─────┬───────┬───────┬─────┐
│ │ C1 │ C2 │ C3 │ C4 │
│ │ Int │ Bool │ Float │ Hex │
├───────┼─────┼───────┼───────┼─────┤
│ Row 1 │ 1 │ false │ 1.0 │ 1 │
│ Row 2 │ 2 │ true │ 2.0 │ 2 │
│ Row 3 │ 3 │ false │ 3.0 │ 3 │
│ Row 4 │ 4 │ true │ 4.0 │ 4 │
│ Row 5 │ 5 │ false │ 5.0 │ 5 │
│ Row 6 │ 6 │ true │ 6.0 │ 6 │
└───────┴─────┴───────┴───────┴─────┘
"""

result = pretty_table(String, data,
["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
row_names = ["Row $i" for i = 1:6])

@test result == expected

expected = """
┌───────────┬─────┬───────┬───────┬─────┐
│ Row names │ C1 │ C2 │ C3 │ C4 │
│ │ Int │ Bool │ Float │ Hex │
├───────────┼─────┼───────┼───────┼─────┤
│ Row 1 │ 1 │ false │ 1.0 │ 1 │
│ Row 2 │ 2 │ true │ 2.0 │ 2 │
│ Row 3 │ 3 │ false │ 3.0 │ 3 │
│ Row 4 │ 4 │ true │ 4.0 │ 4 │
│ Row 5 │ 5 │ false │ 5.0 │ 5 │
│ Row 6 │ 6 │ true │ 6.0 │ 6 │
└───────────┴─────┴───────┴───────┴─────┘
"""

result = pretty_table(String, data,
["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
row_names = ["Row $i" for i = 1:6],
row_name_column_title = "Row names")

@test result == expected

expected = """
┌───────────┬─────┬───────┬───────┬─────┐
│ Row names │ C1 │ C2 │ C3 │ C4 │
│ │ Int │ Bool │ Float │ Hex │
├───────────┼─────┼───────┼───────┼─────┤
│ Row 1 │ 1 │ false │ 1.0 │ 1 │
│ Row 2 │ 2 │ true │ 2.0 │ 2 │
│ Row 3 │ 3 │ false │ 3.0 │ 3 │
│ Row 4 │ 4 │ true │ 4.0 │ 4 │
│ Row 5 │ 5 │ false │ 5.0 │ 5 │
│ Row 6 │ 6 │ true │ 6.0 │ 6 │
└───────────┴─────┴───────┴───────┴─────┘
"""

result = pretty_table(String, data,
["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
row_names = ["Row $i" for i = 1:6],
row_name_column_title = "Row names",
row_name_alignment = :c)

@test result == expected

expected = """
┌───────────┬─────┬───────┬───────┬─── ⋯
│ Row names │ C1 │ C2 │ C3 │ C ⋯
│ │ Int │ Bool │ Float │ He ⋯
├───────────┼─────┼───────┼───────┼─── ⋯
│ Row 1 │ 1 │ false │ 1.0 │ ⋯
│ Row 2 │ 2 │ true │ 2.0 │ ⋯
│ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ ⋯
└───────────┴─────┴───────┴───────┴─── ⋯
"""

result = pretty_table(String, data,
["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
row_names = ["Row $i" for i = 1:6],
row_name_column_title = "Row names",
row_name_alignment = :c,
screen_size = (11,40))
@test result == expected
end

# Test if we can print `missing` and `nothing`
# ==============================================================================

Expand Down

0 comments on commit df54734

Please sign in to comment.