From df54734e83a7a95d5986f3548fa59cd39e1a7a0a Mon Sep 17 00:00:00 2001 From: Ronan Arraes Jardim Chagas Date: Sun, 8 Mar 2020 18:59:38 -0300 Subject: [PATCH] :sparkles: Add option to include row name when printing a table In this commit, only the text backend is supported. This addresses a request mentioned in the issue #18. --- docs/src/man/usage.md | 9 ++++ src/backends/text/print.jl | 57 ++++++++++++++++++++--- src/backends/text/private.jl | 21 +++++++-- src/print.jl | 25 ++++++++++- src/types.jl | 8 +++- test/text_backend.jl | 87 ++++++++++++++++++++++++++++++++++++ 6 files changed, 194 insertions(+), 13 deletions(-) diff --git a/docs/src/man/usage.md b/docs/src/man/usage.md index 1cb9684b..107cfe21 100644 --- a/docs/src/man/usage.md +++ b/docs/src/man/usage.md @@ -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 diff --git a/src/backends/text/print.jl b/src/backends/text/print.jl index 63907e10..d8af7394 100644 --- a/src/backends/text/print.jl +++ b/src/backends/text/print.jl @@ -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, @@ -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,)) @@ -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 # ========================================================================== @@ -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] @@ -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 @@ -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] @@ -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 @@ -376,7 +419,7 @@ 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, @@ -384,7 +427,8 @@ function _pt_text(io, pinfo; 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 @@ -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 # ========================================================================== diff --git a/src/backends/text/private.jl b/src/backends/text/private.jl index 991f46ee..e622a212 100644 --- a/src/backends/text/private.jl +++ b/src/backends/text/private.jl @@ -128,7 +128,7 @@ 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. @@ -136,7 +136,8 @@ 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) @@ -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) @@ -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) @@ -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 diff --git a/src/print.jl b/src/print.jl index be3a3c3e..3581d6f1 100644 --- a/src/print.jl +++ b/src/print.jl @@ -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 @@ -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. @@ -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. @@ -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...) diff --git a/src/types.jl b/src/types.jl index 566d61c2..69cfe004 100644 --- a/src/types.jl +++ b/src/types.jl @@ -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} @@ -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 diff --git a/test/text_backend.jl b/test/text_backend.jl index 563438a2..7231e190 100644 --- a/test/text_backend.jl +++ b/test/text_backend.jl @@ -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` # ==============================================================================