diff --git a/NEWS.md b/NEWS.md index 2f2a6af8e6..a17d83a61b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -67,3 +67,7 @@ ([#2315](https://github.com/JuliaData/DataFrames.jl/pull/2315)) * add rich display support for Markdown cell entries in HTML and LaTeX ([#2346](https://github.com/JuliaData/DataFrames.jl/pull/2346)) +* limit the maximal display width the output can use in `text/plain` before + being truncated (in the `textwidth` sense, excluding `…`) to `32` per column + by default and fix a corner case when no columns are printed in situations when + they are too wide ([2403](https://github.com/JuliaData/DataFrames.jl/pull/2403)) diff --git a/src/abstractdataframe/io.jl b/src/abstractdataframe/io.jl index eda9a60200..5035fbd247 100644 --- a/src/abstractdataframe/io.jl +++ b/src/abstractdataframe/io.jl @@ -98,7 +98,7 @@ function _show(io::IO, ::MIME"text/html", df::AbstractDataFrame; if get(io, :limit, false) tty_rows, tty_cols = displaysize(io) mxrow = min(mxrow, tty_rows) - maxwidths = getmaxwidths(df, io, 1:mxrow, 0:-1, :X, nothing, true, buffer) .+ 2 + maxwidths = getmaxwidths(df, io, 1:mxrow, 0:-1, :X, nothing, true, buffer, 0) .+ 2 mxcol = min(mxcol, searchsortedfirst(cumsum(maxwidths), tty_cols)) end @@ -148,11 +148,11 @@ function _show(io::IO, ::MIME"text/html", df::AbstractDataFrame; write(io, "") elseif cell_val isa SHOW_TABULAR_TYPES write(io, "") - cell = sprint(ourshow, cell_val) + cell = sprint(ourshow, cell_val, 0) write(io, html_escape(cell)) write(io, "") else - cell = sprint(ourshow, cell_val) + cell = sprint(ourshow, cell_val, 0) write(io, "$(html_escape(cell))") end else @@ -270,7 +270,7 @@ function _show(io::IO, ::MIME"text/latex", df::AbstractDataFrame; if get(io, :limit, false) tty_rows, tty_cols = get(io, :displaysize, displaysize(io)) mxrow = min(mxrow, tty_rows) - maxwidths = getmaxwidths(df, io, 1:mxrow, 0:-1, :X, nothing, true, buffer) .+ 2 + maxwidths = getmaxwidths(df, io, 1:mxrow, 0:-1, :X, nothing, true, buffer, 0) .+ 2 mxcol = min(mxcol, searchsortedfirst(cumsum(maxwidths), tty_cols)) end @@ -310,13 +310,13 @@ function _show(io::IO, ::MIME"text/latex", df::AbstractDataFrame; print(io, strip(repr(MIME("text/latex"), cell))) elseif cell isa SHOW_TABULAR_TYPES print(io, "\\emph{") - print(io, latex_escape(sprint(ourshow, cell, context=io))) + print(io, latex_escape(sprint(ourshow, cell, 0, context=io))) print(io, "}") else if showable(MIME("text/latex"), cell) show(io, MIME("text/latex"), cell) else - print(io, latex_escape(sprint(ourshow, cell, context=io))) + print(io, latex_escape(sprint(ourshow, cell, 0, context=io))) end end end @@ -386,7 +386,10 @@ end # ############################################################################## -escapedprint(io::IO, x::Any, escapes::AbstractString) = ourshow(io, x) +escapedprint(io::IO, x::SHOW_TABULAR_TYPES, escapes::AbstractString) = + escapedprint(io, summary(x), escapes) +escapedprint(io::IO, x::Any, escapes::AbstractString) = + escapedprint(io, sprint(print, x), escapes) escapedprint(io::IO, x::AbstractString, escapes::AbstractString) = escape_string(io, x, escapes) @@ -427,7 +430,7 @@ function printtable(io::IO, r = repr(cell) escapedprint(io, chomp(r), quotestr) print(io, quotemark) - elseif ! (etypes[j] <: Real) + elseif !(etypes[j] <: Real) print(io, quotemark) escapedprint(io, cell, quotestr) print(io, quotemark) diff --git a/src/abstractdataframe/iteration.jl b/src/abstractdataframe/iteration.jl index 7bb74cdac3..3805a0f4f2 100644 --- a/src/abstractdataframe/iteration.jl +++ b/src/abstractdataframe/iteration.jl @@ -252,11 +252,12 @@ function Base.show(io::IO, dfrs::DataFrameRows; splitcols = get(io, :limit, false), rowlabel::Symbol = :Row, summary::Bool = true, - eltypes::Bool = true) + eltypes::Bool = true, + truncate::Int = 32) df = parent(dfrs) summary && print(io, "$(nrow(df))×$(ncol(df)) DataFrameRows") _show(io, df, allrows=allrows, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, summary=false, eltypes=eltypes) + rowlabel=rowlabel, summary=false, eltypes=eltypes, truncstring=truncate) end Base.show(io::IO, mime::MIME"text/plain", dfrs::DataFrameRows; @@ -265,9 +266,10 @@ Base.show(io::IO, mime::MIME"text/plain", dfrs::DataFrameRows; splitcols = get(io, :limit, false), rowlabel::Symbol = :Row, summary::Bool = true, - eltypes::Bool = true) = + eltypes::Bool = true, + truncate::Int = 32) = show(io, dfrs, allrows=allrows, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, summary=summary, eltypes=eltypes) + rowlabel=rowlabel, summary=summary, eltypes=eltypes, truncate=truncate) Base.show(dfrs::DataFrameRows; allrows::Bool = !get(stdout, :limit, true), @@ -275,9 +277,10 @@ Base.show(dfrs::DataFrameRows; splitcols = get(stdout, :limit, true), rowlabel::Symbol = :Row, summary::Bool = true, - eltypes::Bool = true) = + eltypes::Bool = true, + truncate::Int = 32) = show(stdout, dfrs, allrows=allrows, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, summary=summary, eltypes=eltypes) + rowlabel=rowlabel, summary=summary, eltypes=eltypes, truncate=truncate) function Base.show(io::IO, dfcs::DataFrameColumns; allrows::Bool = !get(io, :limit, false), @@ -285,11 +288,12 @@ function Base.show(io::IO, dfcs::DataFrameColumns; splitcols = get(io, :limit, false), rowlabel::Symbol = :Row, summary::Bool = true, - eltypes::Bool = true) + eltypes::Bool = true, + truncate::Int = 32) df = parent(dfcs) summary && print(io, "$(nrow(df))×$(ncol(df)) DataFrameColumns") _show(io, parent(dfcs), allrows=allrows, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, summary=false, eltypes=eltypes) + rowlabel=rowlabel, summary=false, eltypes=eltypes, truncstring=truncate) end Base.show(io::IO, mime::MIME"text/plain", dfcs::DataFrameColumns; @@ -298,9 +302,10 @@ Base.show(io::IO, mime::MIME"text/plain", dfcs::DataFrameColumns; splitcols = get(io, :limit, false), rowlabel::Symbol = :Row, summary::Bool = true, - eltypes::Bool = true) = + eltypes::Bool = true, + truncate::Int = 32) = show(io, dfcs, allrows=allrows, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, summary=summary, eltypes=eltypes) + rowlabel=rowlabel, summary=summary, eltypes=eltypes, truncate=truncate) Base.show(dfcs::DataFrameColumns; allrows::Bool = !get(stdout, :limit, true), @@ -308,9 +313,10 @@ Base.show(dfcs::DataFrameColumns; splitcols = get(stdout, :limit, true), rowlabel::Symbol = :Row, summary::Bool = true, - eltypes::Bool = true) = + eltypes::Bool = true, + truncate::Int = 32) = show(stdout, dfcs, allrows=allrows, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, summary=summary, eltypes=eltypes) + rowlabel=rowlabel, summary=summary, eltypes=eltypes, truncate=truncate) """ mapcols(f::Union{Function,Type}, df::AbstractDataFrame) diff --git a/src/abstractdataframe/show.jl b/src/abstractdataframe/show.jl index 4bbc203943..7e23975f2c 100644 --- a/src/abstractdataframe/show.jl +++ b/src/abstractdataframe/show.jl @@ -3,24 +3,38 @@ Base.summary(df::AbstractDataFrame) = Base.summary(io::IO, df::AbstractDataFrame) = print(io, summary(df)) """ - DataFrames.ourstrwidth(io::IO, x::Any, buffer) + DataFrames.ourstrwidth(io::IO, x::Any, buffer::IOBuffer, truncstring::Int) Determine the number of characters that would be used to print a value. """ -function ourstrwidth(io::IO, x::Any, buffer::IOBuffer) +function ourstrwidth(io::IO, x::Any, buffer::IOBuffer, truncstring::Int) truncate(buffer, 0) - ourshow(IOContext(buffer, :compact=>get(io, :compact, true)), x) + ourshow(IOContext(buffer, :compact=>get(io, :compact, true)), x, truncstring) return textwidth(String(take!(buffer))) end +function truncatestring(s::AbstractString, truncstring::Int) + truncstring <= 0 && return s + totalwidth = 0 + for (i, c) in enumerate(s) + totalwidth += textwidth(c) + if totalwidth > truncstring + return first(s, i-1) * '…' + end + end + return s +end + """ - DataFrames.ourshow(io::IO, x::Any) + DataFrames.ourshow(io::IO, x::Any, truncstring::Int) Render a value to an `IO` object compactly and omitting type information, by calling 3-argument `show`, or 2-argument `show` if the former contains line breaks. Unlike `show`, render strings without surrounding quote marks. +`truncstring` indicates the approximate number of text characters width to truncate +the output (if it is a non-positive value then no truncation is applied). """ -function ourshow(io::IO, x::Any; styled::Bool=false) +function ourshow(io::IO, x::Any, truncstring::Int; styled::Bool=false) io_ctx = IOContext(io, :compact=>get(io, :compact, true), :typeinfo=>typeof(x)) # This mirrors the behavior of Base.print_matrix_row @@ -38,6 +52,8 @@ function ourshow(io::IO, x::Any; styled::Bool=false) sx = escape_string(chop(sx, head=1, tail=1), "") end + sx = truncatestring(sx, truncstring) + if styled printstyled(io_ctx, sx, color=:light_black) else @@ -48,22 +64,27 @@ end const SHOW_TABULAR_TYPES = Union{AbstractDataFrame, DataFrameRow, DataFrameRows, DataFrameColumns, GroupedDataFrame} -ourshow(io::IO, x::AbstractString) = escape_string(io, x, "") -ourshow(io::IO, x::CategoricalValue{<:AbstractString}) = escape_string(io, get(x), "") -ourshow(io::IO, x::Symbol) = ourshow(io, string(x)) -ourshow(io::IO, x::Nothing; styled::Bool=false) = ourshow(io, "", styled=styled) -ourshow(io::IO, x::SHOW_TABULAR_TYPES; styled::Bool=false) = - ourshow(io, summary(x), styled=styled) -function ourshow(io::IO, x::Markdown.MD) +ourshow(io::IO, x::AbstractString, truncstring::Int) = + escape_string(io, truncatestring(x, truncstring), "") +ourshow(io::IO, x::CategoricalValue{<:AbstractString}, truncstring::Int) = + ourshow(io, get(x), truncstring) +ourshow(io::IO, x::Symbol, truncstring::Int) = ourshow(io, string(x), truncstring) +ourshow(io::IO, x::Nothing, truncstring::Int; styled::Bool=false) = + ourshow(io, "", styled=styled, truncstring) +ourshow(io::IO, x::SHOW_TABULAR_TYPES, truncstring::Int; styled::Bool=false) = + ourshow(io, summary(x), truncstring, styled=styled) + +function ourshow(io::IO, x::Markdown.MD, truncstring::Int) r = repr(x) - len = min(length(r, 1, something(findfirst(==('\n'), r), lastindex(r)+1)-1), 32) + truncstring <= 0 && return chomp(truncstring) + len = min(length(r, 1, something(findfirst(==('\n'), r), lastindex(r)+1)-1), truncstring) return print(io, len < length(r) - 1 ? first(r, len)*'…' : first(r, len)) end # AbstractChar: https://github.com/JuliaLang/julia/pull/34730 (1.5.0-DEV.261) # Irrational: https://github.com/JuliaLang/julia/pull/34741 (1.5.0-DEV.266) if VERSION < v"1.5.0-DEV.261" || VERSION < v"1.5.0-DEV.266" - function ourshow(io::IO, x::T) where T <: Union{AbstractChar, Irrational} + function ourshow(io::IO, x::T, truncstring::Int) where T <: Union{AbstractChar, Irrational} io = IOContext(io, :compact=>get(io, :compact, true), :typeinfo=>typeof(x)) show(io, x) end @@ -164,38 +185,43 @@ function getmaxwidths(df::AbstractDataFrame, rowlabel::Symbol, rowid::Union{Integer, Nothing}, show_eltype::Bool, - buffer::IOBuffer) + buffer::IOBuffer, + truncstring::Int) maxwidths = Vector{Int}(undef, size(df, 2) + 1) - undefstrwidth = ourstrwidth(io, "#undef", buffer) + undefstrwidth = ourstrwidth(io, "#undef", buffer, truncstring) j = 1 for (name, col) in pairs(eachcol(df)) # (1) Consider length of column name - maxwidth = ourstrwidth(io, name, buffer) + # do not truncate column name + maxwidth = ourstrwidth(io, name, buffer, 0) # (2) Consider length of longest entry in that column for indices in (rowindices1, rowindices2), i in indices if isassigned(col, i) - maxwidth = max(maxwidth, ourstrwidth(io, col[i], buffer)) + maxwidth = max(maxwidth, ourstrwidth(io, col[i], buffer, truncstring)) else maxwidth = max(maxwidth, undefstrwidth) end end if show_eltype - maxwidths[j] = max(maxwidth, ourstrwidth(io, compacttype(eltype(col)), buffer)) + # do not truncate eltype name + maxwidths[j] = max(maxwidth, ourstrwidth(io, compacttype(eltype(col)), buffer, 0)) else maxwidths[j] = maxwidth end j += 1 end + # do not truncate rowlabel if rowid isa Nothing rowmaxwidth1 = isempty(rowindices1) ? 0 : ndigits(maximum(rowindices1)) rowmaxwidth2 = isempty(rowindices2) ? 0 : ndigits(maximum(rowindices2)) - maxwidths[j] = max(max(rowmaxwidth1, rowmaxwidth2), ourstrwidth(io, rowlabel, buffer)) + maxwidths[j] = max(max(rowmaxwidth1, rowmaxwidth2), + ourstrwidth(io, rowlabel, buffer, 0)) else - maxwidths[j] = max(ndigits(rowid), ourstrwidth(io, rowlabel, buffer)) + maxwidths[j] = max(ndigits(rowid), ourstrwidth(io, rowlabel, buffer, 0)) end return maxwidths @@ -320,7 +346,8 @@ function showrowindices(io::IO, leftcol::Int, rightcol::Int, rowid::Union{Integer, Nothing}, - buffer::IOBuffer) + buffer::IOBuffer, + truncstring::Int) rowmaxwidth = maxwidths[end] for i in rowindices @@ -340,15 +367,15 @@ function showrowindices(io::IO, strlen = 0 if isassigned(df[!, j], i) s = df[i, j] - strlen = ourstrwidth(io, s, buffer) + strlen = ourstrwidth(io, s, buffer, truncstring) if ismissing(s) || s isa SHOW_TABULAR_TYPES - ourshow(io, s, styled=true) + ourshow(io, s, truncstring, styled=true) else - ourshow(io, s) + ourshow(io, s, truncstring) end else - strlen = ourstrwidth(io, "#undef", buffer) - ourshow(io, "#undef", styled=true) + strlen = ourstrwidth(io, "#undef", buffer, truncstring) + ourshow(io, "#undef", truncstring, styled=true) end padding = maxwidths[j] - strlen for _ in 1:padding @@ -441,7 +468,8 @@ function showrows(io::IO, displaysummary::Bool, eltypes::Bool, rowid::Union{Integer, Nothing}, - buffer::IOBuffer) + buffer::IOBuffer, + truncstring::Int) ncols = size(df, 2) @@ -457,26 +485,39 @@ function showrows(io::IO, nchunks = allcols ? length(chunkbounds) - 1 : min(length(chunkbounds) - 1, 1) header = displaysummary ? summary(df) : "" + cols_other_chunks = chunkbounds[end] - chunkbounds[2] if !allcols && length(chunkbounds) > 2 - header *= ". Omitted printing of $(chunkbounds[end] - chunkbounds[2]) columns" + # if we print only one chunk and it does not fit the screen give up + if cols_other_chunks == ncols + print(io, header * ". Omitted printing of all columns as they do " * + "not fit the display size") + return + end + header *= ". Omitted printing of $cols_other_chunks columns" end + println(io, header) for chunkindex in 1:nchunks leftcol = chunkbounds[chunkindex] + 1 rightcol = chunkbounds[chunkindex + 1] + # nothing to print in this chunk + leftcol > rightcol && continue + # Print column names @printf io "│ %s" rowlabel - padding = rowmaxwidth - ourstrwidth(io, rowlabel, buffer) + # do not truncate rowlabel + padding = rowmaxwidth - ourstrwidth(io, rowlabel, buffer, 0) for itr in 1:padding write(io, ' ') end print(io, " │ ") for j in leftcol:rightcol s = _names(df)[j] - ourshow(io, s) - padding = maxwidths[j] - ourstrwidth(io, s, buffer) + # do not truncate column names + ourshow(io, s, 0) + padding = maxwidths[j] - ourstrwidth(io, s, buffer, 0) for itr in 1:padding write(io, ' ') end @@ -498,7 +539,8 @@ function showrows(io::IO, for j in leftcol:rightcol s = compacttype(eltype(df[!, j]), maxwidths[j], false) printstyled(io, s, color=:light_black) - padding = maxwidths[j] - ourstrwidth(io, s, buffer) + # do not truncate eltype + padding = maxwidths[j] - ourstrwidth(io, s, buffer, 0) for itr in 1:padding write(io, ' ') end @@ -529,23 +571,13 @@ function showrows(io::IO, write(io, '\n') # Print main table body, potentially in two abbreviated sections - showrowindices(io, - df, - rowindices1, - maxwidths, - leftcol, - rightcol, - rowid, buffer) + showrowindices(io, df, rowindices1, maxwidths, leftcol, rightcol, + rowid, buffer, truncstring) if !isempty(rowindices2) print(io, "\n⋮\n") - showrowindices(io, - df, - rowindices2, - maxwidths, - leftcol, - rightcol, - rowid, buffer) + showrowindices(io, df, rowindices2, maxwidths, leftcol, rightcol, + rowid, buffer, truncstring) end # Print newlines to separate chunks @@ -565,7 +597,8 @@ function _show(io::IO, rowlabel::Symbol = :Row, summary::Bool = true, eltypes::Bool = true, - rowid=nothing) + rowid=nothing, + truncstring::Int) _check_consistency(df) # we will pass around this buffer to avoid its reallocation in ourstrwidth @@ -590,19 +623,11 @@ function _show(io::IO, rowindices1 = 1:bound rowindices2 = max(bound + 1, nrows - nrowssubset + 1):nrows end - maxwidths = getmaxwidths(df, io, rowindices1, rowindices2, rowlabel, rowid, eltypes, buffer) + maxwidths = getmaxwidths(df, io, rowindices1, rowindices2, rowlabel, rowid, + eltypes, buffer, truncstring) width = getprintedwidth(maxwidths) - showrows(io, - df, - rowindices1, - rowindices2, - maxwidths, - splitcols, - allcols, - rowlabel, - summary, - eltypes, - rowid, buffer) + showrows(io, df, rowindices1, rowindices2, maxwidths, splitcols, allcols, + rowlabel, summary, eltypes, rowid, buffer, truncstring) return end @@ -614,7 +639,8 @@ end splitcols::Bool = get(io, :limit, false), rowlabel::Symbol = :Row, summary::Bool = true, - eltypes::Bool = true) + eltypes::Bool = true, + truncate::Int = 32) Render a data frame to an I/O stream. The specific visual representation chosen depends on the width of the display. @@ -643,6 +669,9 @@ while `splitcols` defaults to `true`. - `rowlabel::Symbol = :Row`: The label to use for the column containing row numbers. - `summary::Bool = true`: Whether to print a brief string summary of the data frame. - `eltypes::Bool = true`: Whether to print the column types under column names. +- `truncate::Int = 32`: the maximal display width the output can use before + being truncated (in the `textwidth` sense, excluding `…`). + If `truncate` is 0 or less, no truncation is applied. # Examples ```jldoctest @@ -667,9 +696,10 @@ Base.show(io::IO, splitcols = get(io, :limit, false), rowlabel::Symbol = :Row, summary::Bool = true, - eltypes::Bool = true) = + eltypes::Bool = true, + truncate::Int = 32) = _show(io, df, allrows=allrows, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, summary=summary, eltypes=eltypes) + rowlabel=rowlabel, summary=summary, eltypes=eltypes, truncstring=truncate) Base.show(df::AbstractDataFrame; allrows::Bool = !get(stdout, :limit, true), @@ -677,7 +707,8 @@ Base.show(df::AbstractDataFrame; splitcols = get(stdout, :limit, true), rowlabel::Symbol = :Row, summary::Bool = true, - eltypes::Bool = true) = + eltypes::Bool = true, + truncate::Int = 32) = show(stdout, df, allrows=allrows, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, summary=summary, eltypes=eltypes) + rowlabel=rowlabel, summary=summary, eltypes=eltypes, truncate=truncate) diff --git a/src/dataframerow/show.jl b/src/dataframerow/show.jl index ba7c030d64..a4b2016a83 100644 --- a/src/dataframerow/show.jl +++ b/src/dataframerow/show.jl @@ -2,25 +2,28 @@ function Base.show(io::IO, dfr::DataFrameRow; allcols::Bool = !get(io, :limit, false), splitcols = get(io, :limit, false), rowlabel::Symbol = :Row, - eltypes::Bool = true) + eltypes::Bool = true, + truncate::Int = 32) r, c = parentindices(dfr) print(io, "DataFrameRow") _show(io, view(parent(dfr), [r], c), allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, summary=false, rowid=r, eltypes=eltypes) + rowlabel=rowlabel, summary=false, rowid=r, eltypes=eltypes, truncstring=truncate) end Base.show(io::IO, mime::MIME"text/plain", dfr::DataFrameRow; allcols::Bool = !get(io, :limit, false), splitcols = get(io, :limit, false), rowlabel::Symbol = :Row, - eltypes::Bool = true) = + eltypes::Bool = true, + truncate::Int = 32) = show(io, dfr, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, eltypes=eltypes) + rowlabel=rowlabel, eltypes=eltypes, truncate=truncate) Base.show(dfr::DataFrameRow; allcols::Bool = !get(stdout, :limit, true), splitcols = get(stdout, :limit, true), rowlabel::Symbol = :Row, - eltypes::Bool = true) = + eltypes::Bool = true, + truncate::Int = 32) = show(stdout, dfr, allcols=allcols, splitcols=splitcols, - rowlabel=rowlabel, eltypes=eltypes) + rowlabel=rowlabel, eltypes=eltypes, truncate=truncate) diff --git a/src/groupeddataframe/show.jl b/src/groupeddataframe/show.jl index aeab3c1938..37948d957c 100644 --- a/src/groupeddataframe/show.jl +++ b/src/groupeddataframe/show.jl @@ -12,7 +12,8 @@ function Base.show(io::IO, gd::GroupedDataFrame; allcols::Bool = !get(io, :limit, false), splitcols::Bool = get(io, :limit, false), rowlabel::Symbol = :Row, - summary::Bool = true) + summary::Bool = true, + truncate::Int = 32) N = length(gd) summary && Base.summary(io, gd) @@ -29,7 +30,7 @@ function Base.show(io::IO, gd::GroupedDataFrame; join(io, identified_groups, ", ") show(io, gd[i], summary=false, - allrows=allrows, allcols=allcols, rowlabel=rowlabel) + allrows=allrows, allcols=allcols, rowlabel=rowlabel, truncate=truncate) end else if N > 0 @@ -43,7 +44,7 @@ function Base.show(io::IO, gd::GroupedDataFrame; join(io, identified_groups, ", ") show(io, gd[1], summary=false, - allrows=allrows, allcols=allcols, rowlabel=rowlabel) + allrows=allrows, allcols=allcols, rowlabel=rowlabel, truncate=truncate) end if N > 1 nrows = size(gd[N], 1) @@ -56,7 +57,7 @@ function Base.show(io::IO, gd::GroupedDataFrame; join(io, identified_groups, ", ") show(io, gd[N], summary=false, - allrows=allrows, allcols=allcols, rowlabel=rowlabel) + allrows=allrows, allcols=allcols, rowlabel=rowlabel, truncate=truncate) end end end @@ -67,8 +68,9 @@ function Base.show(df::GroupedDataFrame; allgroups::Bool = !get(stdout, :limit, true), splitcols::Bool = get(stdout, :limit, true), rowlabel::Symbol = :Row, - summary::Bool = true) # -> Nothing + summary::Bool = true, + truncate::Int = 32) # -> Nothing return show(stdout, df, allrows=allrows, allcols=allcols, allgroups=allgroups, - splitcols=splitcols, rowlabel=rowlabel, summary=summary) + splitcols=splitcols, rowlabel=rowlabel, summary=summary, truncate=truncate) end diff --git a/test/io.jl b/test/io.jl index 6df042e064..99bf28dc88 100644 --- a/test/io.jl +++ b/test/io.jl @@ -180,9 +180,9 @@ end @test sprint(DataFrames.printtable, df) == """ "A","B","C","D","E","F","G","H" - 1,"'a'","A","a","A","1",missing,missing - 2,"'b'","B","b","B","2",missing,missing - 3,"'c'","C","c",missing,"3",missing,missing + 1,"a","A","a","A","1",missing,missing + 2,"b","B","b","B","2",missing,missing + 3,"c","C","c",missing,"3",missing,missing """ end @@ -531,18 +531,18 @@ end @test str == """ 9×2 DataFrame - │ Row │ A │ B │ - │ │ Int64 │ Any │ - ├─────┼───────┼────────────────────────────────────────────────┤ - │ 1 │ 1 │ 9×2 DataFrame │ - │ 2 │ 2 │ 2-element DataFrameRow │ - │ 3 │ 3 │ 1×2 SubDataFrame │ - │ 4 │ 4 │ 9-element DataFrameRows │ - │ 5 │ 5 │ 2-element DataFrameColumns │ - │ 6 │ 6 │ GroupedDataFrame with 9 groups based on key: A │ - │ 7 │ 7 │ missing │ - │ 8 │ 8 │ │ - │ 9 │ 9 │ #undef │""" + │ Row │ A │ B │ + │ │ Int64 │ Any │ + ├─────┼───────┼───────────────────────────────────┤ + │ 1 │ 1 │ 9×2 DataFrame │ + │ 2 │ 2 │ 2-element DataFrameRow │ + │ 3 │ 3 │ 1×2 SubDataFrame │ + │ 4 │ 4 │ 9-element DataFrameRows │ + │ 5 │ 5 │ 2-element DataFrameColumns │ + │ 6 │ 6 │ GroupedDataFrame with 9 groups b… │ + │ 7 │ 7 │ missing │ + │ 8 │ 8 │ │ + │ 9 │ 9 │ #undef │""" io = IOBuffer() @@ -550,18 +550,18 @@ end str = String(take!(io)) @test str == """ 9×2 DataFrame - │ Row │ A │ B │ - │ │ \e[90mInt64\e[39m │ \e[90mAny\e[39m │ - ├─────┼───────┼────────────────────────────────────────────────┤ - │ 1 │ 1 │ \e[90m9×2 DataFrame\e[39m │ - │ 2 │ 2 │ \e[90m2-element DataFrameRow\e[39m │ - │ 3 │ 3 │ \e[90m1×2 SubDataFrame\e[39m │ - │ 4 │ 4 │ \e[90m9-element DataFrameRows\e[39m │ - │ 5 │ 5 │ \e[90m2-element DataFrameColumns\e[39m │ - │ 6 │ 6 │ \e[90mGroupedDataFrame with 9 groups based on key: A\e[39m │ - │ 7 │ 7 │ \e[90mmissing\e[39m │ - │ 8 │ 8 │ │ - │ 9 │ 9 │ \e[90m#undef\e[39m │""" + │ Row │ A │ B │ + │ │ \e[90mInt64\e[39m │ \e[90mAny\e[39m │ + ├─────┼───────┼───────────────────────────────────┤ + │ 1 │ 1 │ \e[90m9×2 DataFrame\e[39m │ + │ 2 │ 2 │ \e[90m2-element DataFrameRow\e[39m │ + │ 3 │ 3 │ \e[90m1×2 SubDataFrame\e[39m │ + │ 4 │ 4 │ \e[90m9-element DataFrameRows\e[39m │ + │ 5 │ 5 │ \e[90m2-element DataFrameColumns\e[39m │ + │ 6 │ 6 │ \e[90mGroupedDataFrame with 9 groups b…\e[39m │ + │ 7 │ 7 │ \e[90mmissing\e[39m │ + │ 8 │ 8 │ │ + │ 9 │ 9 │ \e[90m#undef\e[39m │""" io = IOBuffer() @@ -604,35 +604,143 @@ end @test_throws UndefRefError show(io, MIME("text/csv"), df) @test_throws UndefRefError show(io, MIME("text/tab-separated-values"), df) + df[end, 2] = "\"" + push!(df, (10, Symbol("\""))) + push!(df, (11, '"')) io = IOBuffer() - show(io, MIME("text/csv"), df[1:end-1, :]) + show(io, MIME("text/csv"), df) str = String(take!(io)) @test str == """ "A","B" - 1,"9×2 DataFrame" + 1,"11×2 DataFrame" 2,"2-element DataFrameRow" 3,"1×2 SubDataFrame" - 4,"9-element DataFrameRows" + 4,"11-element DataFrameRows" 5,"2-element DataFrameColumns" 6,"GroupedDataFrame with 9 groups based on key: A" 7,missing 8,nothing + 9,"\\"" + 10,"\\"" + 11,"\\"" """ io = IOBuffer() - show(io, MIME("text/tab-separated-values"), df[1:end-1, :]) + show(io, MIME("text/tab-separated-values"), df) str = String(take!(io)) @test str == """ "A"\t"B" - 1\t"9×2 DataFrame" + 1\t"11×2 DataFrame" 2\t"2-element DataFrameRow" 3\t"1×2 SubDataFrame" - 4\t"9-element DataFrameRows" + 4\t"11-element DataFrameRows" 5\t"2-element DataFrameColumns" 6\t"GroupedDataFrame with 9 groups based on key: A" 7\tmissing 8\tnothing + 9\t"\\"" + 10\t"\\"" + 11\t"\\"" """ end +@testset "check truncate keyword argument" begin + df = DataFrame(x = "0123456789"^10) + + # no truncation + io = IOBuffer() + show(io, MIME("text/html"), df) + str = String(take!(io)) + @test str == "" * + "" * + "

1 rows × 1 columns

" * + ""* + "
x
String
101234567890123456789012345678901234567890123456789" * + "01234567890123456789012345678901234567890123456789
" + + # no truncation + io = IOBuffer() + show(io, MIME("text/latex"), df) + str = String(take!(io)) + @test str == """ + \\begin{tabular}{r|c} + \t& x\\\\ + \t\\hline + \t& String\\\\ + \t\\hline + \t1 & 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 \\\\ + \\end{tabular} + """ + + # no truncation + io = IOBuffer() + show(io, MIME("text/csv"), df) + str = String(take!(io)) + @test str == "\"x\"\n\"01234567890123456789012345678901234567890123456789" * + "01234567890123456789012345678901234567890123456789\"\n" + + # no truncation + io = IOBuffer() + show(io, MIME("text/tab-separated-values"), df) + str = String(take!(io)) + @test str == "\"x\"\n\"01234567890123456789012345678901234567890123456789" * + "01234567890123456789012345678901234567890123456789\"\n" + + # default truncation + io = IOBuffer() + show(io, MIME("text/plain"), df) + str = String(take!(io)) + @test str == """ + 1×1 DataFrame + │ Row │ x │ + │ │ String │ + ├─────┼───────────────────────────────────┤ + │ 1 │ 01234567890123456789012345678901… │""" + + io = IOBuffer() + show(io, df) + str = String(take!(io)) + @test str == """ + 1×1 DataFrame + │ Row │ x │ + │ │ String │ + ├─────┼───────────────────────────────────┤ + │ 1 │ 01234567890123456789012345678901… │""" + + # no truncation + io = IOBuffer() + show(io, df, truncate=0) + str = String(take!(io)) + @test str == """ + 1×1 DataFrame + │ Row │ x │ + │ │ String │ + ├─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ 1 │ 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 │""" + + # custom truncation + io = IOBuffer() + show(io, df, truncate=1) + str = String(take!(io)) + @test str == """ + 1×1 DataFrame + │ Row │ x │ + │ │ String │ + ├─────┼────────┤ + │ 1 │ 0… │""" + + + df = DataFrame(x12345678901234567890 = "0123456789"^10) + io = IOBuffer() + show(io, df, truncate=1, rowlabel=:r12345678901234567890) + str = String(take!(io)) + @test str == """ + 1×1 DataFrame + │ r12345678901234567890 │ x12345678901234567890 │ + │ │ String │ + ├───────────────────────┼───────────────────────┤ + │ 1 │ 0… │""" + +end + end # module diff --git a/test/show.jl b/test/show.jl index c8392d3bc7..be6d563833 100644 --- a/test/show.jl +++ b/test/show.jl @@ -500,4 +500,49 @@ end │ 1 │ 1:2 │""" end +@testset "wide output and column trimming" begin + df = DataFrame(x = "0123456789"^4) + io = IOBuffer() + show(io, df) + str = String(take!(io)) + @test str == """ + 1×1 DataFrame + │ Row │ x │ + │ │ String │ + ├─────┼───────────────────────────────────┤ + │ 1 │ 01234567890123456789012345678901… │""" + + io = IOContext(IOBuffer(), :displaysize=>(10,10), :limit=>true) + show(io, df) + str = String(take!(io.io)) + @test str === "1×1 DataFrame. Omitted printing of all columns as they do not fit the display size" + + df = DataFrame(x = "0123456789"^4, y = "0123456789"^4) + io = IOContext(IOBuffer(), :displaysize=>(10,10), :limit=>true) + show(io, df, splitcols=true, allcols=true) + str = String(take!(io.io)) + @test str === """ + 1×2 DataFrame + │ Row │ x │ + │ │ String │ + ├─────┼───────────────────────────────────┤ + │ 1 │ 01234567890123456789012345678901… │ + + │ Row │ y │ + │ │ String │ + ├─────┼───────────────────────────────────┤ + │ 1 │ 01234567890123456789012345678901… │""" + + df = DataFrame(x = "😄"^20) + io = IOBuffer() + show(io, df) + str = String(take!(io)) + @test str === """ + 1×1 DataFrame + │ Row │ x │ + │ │ String │ + ├─────┼───────────────────────────────────┤ + │ 1 │ 😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄😄… │""" +end + end # module