Skip to content

Commit

Permalink
Show methods for HTML and LaTeX (#480)
Browse files Browse the repository at this point in the history
* refactor markdown show methods

* show methods for latex and html

* formatting

* update mime docs

* escape sigma and chi square for latex

* document escape behavior

* xelatex

* more escaping

* news entry

* truncate to avoid exponent formatting in tests

* more exponent formatting

* tiny platform differences

* osx you are a pain
  • Loading branch information
palday authored Mar 4, 2021
1 parent 8ccb580 commit 160783d
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 172 deletions.
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ Run-time formula syntax
* Methods for `Base./(::AbstractTerm, ::AbstractTerm)` are added, allowing
nesting syntax to be used with `Term`s at run-time as well [#470]


MixedModels v3.3.0 Release Notes
========================
* HTML and LaTeX `show` methods for `MixedModel`, `BlockDescription`,
`LikelihoodRatioTest`, `OptSummary` and `VarCorr`. Note that the interface for
these is not yet completely stable. In particular, rounding behavior may
change. [#480]

MixedModels v3.2.0 Release Notes
========================
* Markdown `show` methods for `MixedModel`, `BlockDescription`,
Expand Down
36 changes: 30 additions & 6 deletions docs/src/mime.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ For example, DataFrames are converted into nice HTML tables.
In MixedModels, we recently (v3.2.0) introduced limited support for such pretty printing.
(For more details on how the print and display system in Julia works, check out [this NextJournal post](https://nextjournal.com/sdanisch/julias-display-system).)

In particular, we have defined Markdown output, i.e. `show` methods, for our types, which can be easily translated into HTML, LaTeX or even a MS Word Document using tools such as [pandoc](https://pandoc.org/).
In particular, we have defined Markdown, HTML and LaTeX output, i.e. `show` methods, for our types.
Note that the Markdown output can also be easily and more flexibly translated into HTML, LaTeX (e.g. with `booktabs`) or even a MS Word Document using tools such as [pandoc](https://pandoc.org/).
Packages like `IJulia` and `Documenter` can often detect the presence of these display options and use them automatically.


Expand Down Expand Up @@ -56,19 +57,42 @@ m1 = fit(MixedModel, @formula(reaction ~ 1 + days + (1+days|subj)), MixedModels.
MixedModels.likelihoodratiotest(m0,m1)
```

To explicitly invoke this behavior, we must specify the right `show` method:
To explicitly invoke this behavior, we must specify the right `show` method.
(The raw and not rendered output is intentionally shown here.)
```julia
show(MIME("text/markdown"), m1)
```
```@example Main
println(sprint(show, MIME("text/markdown"), kbm)) # hide
```
(The raw and not rendered output is intentionally shown here.)

In the future, we may directly support HTML and LaTeX as MIME types.
```julia
show(MIME("text/html"), m1)
```
```@example Main
println(sprint(show, MIME("text/html"), kbm)) # hide
```
Note for that LaTeX, the column labels for the random effects are slightly changed: σ is placed into math mode and escaped and the grouping variable is turned into a subscript.
Similarly for the likelihood ratio test, the χ² is escaped into math mode.
This transformation improves pdfLaTeX and journal compatibility, but also means that XeLaTeX and LuaTeX may use a different font at this point.
```julia
show(MIME("text/latex"), m1)
```
```@example Main
println(sprint(show, MIME("text/latex"), kbm)) # hide
```
This escaping behavior can be disabled by specifying `"text/xelatex"` as the MIME type.
(Note that other symbols may still be escaped, as the internal conversion uses the `Markdown` module from the standard library, which performs some escaping on its own.)
```julia
show(MIME("text/xelatex"), m1)
```
```@example Main
println(sprint(show, MIME("text/xelatex"), kbm)) # hide
```

This output can also be written directly to file:

```julia
show(open("model.md", "w"), MIME("text/markdown"), kbm)
open("model.md", "w") do io
show(io, MIME("text/markdown"), kbm)
end
```
2 changes: 1 addition & 1 deletion src/MixedModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,6 @@ include("simulate.jl")
include("bootstrap.jl")
include("blockdescription.jl")
include("grouping.jl")
include("mdshow.jl")
include("mimeshow.jl")

end # module
89 changes: 63 additions & 26 deletions src/mdshow.jl → src/mimeshow.jl
Original file line number Diff line number Diff line change
@@ -1,28 +1,68 @@
# for this type of union, the compiler will actually generate the necessary methods
# but it's also type stable either way
_MdTypes = Union{BlockDescription, LikelihoodRatioTest, OptSummary, VarCorr, MixedModel}
Base.show(mime::MIME, x::_MdTypes) = Base.show(Base.stdout, mime, x)
Base.show(mime::MIME, x::_MdTypes) = show(Base.stdout, mime, x)

Base.show(io::IO, ::MIME"text/markdown", x::_MdTypes) = show(io, Markdown.MD(_markdown(x)))
# let's not discuss why we need show above and println below,
# nor what happens if we try display instead :)
Base.show(io::IO, ::MIME"text/html", x::_MdTypes) = println(io, Markdown.html(_markdown(x)))
# print and println because Julia already adds a newline line
Base.show(io::IO, ::MIME"text/latex", x::_MdTypes) = print(io, Markdown.latex(_markdown(x)))
Base.show(io::IO, ::MIME"text/xelatex", x::_MdTypes) = print(io, Markdown.latex(_markdown(x)))

# not sure why this escaping doesn't work automatically
# FIXME: find out a way to get the stdlib to do this
function Base.show(io::IO, ::MIME"text/html", x::OptSummary)
out = Markdown.html(_markdown(x))
out = replace(out, r"&#96;([^[:space:]]*)&#96;" => s"<code>\1</code>")
out = replace(out, r"\*\*(.*?)\*\*" => s"<b>\1</b>")
println(io, out)
end

function Base.show(io::IO, ::MIME"text/latex", x::OptSummary)
out = Markdown.latex(_markdown(x))
out = replace(out, r"`([^[:space:]]*)`" => s"\\texttt{\1}")
out = replace(out, r"\*\*(.*?)\*\*" => s"\\textbf{\1}")
print(io, out)
end

function Base.show(io::IO, ::MIME"text/latex", x::MixedModel)
la = Markdown.latex(_markdown(x))
# take advantage of subscripting
# including preceding & prevents capturing coefficients
la = replace(la, r"& σ\\_([[:alnum:]]*) " => s"& $\\sigma_\\text{\1}$ ")
print(io, la)
end

function Base.show(io::IO, ::MIME"text/latex", x::LikelihoodRatioTest)
la = Markdown.latex(_markdown(x))
# take advantage of subscripting
# including preceding & prevents capturing coefficients
la = replace(la, r"χ²" => s"$\\chi^2$")
print(io, la)
end

function Base.show(io::IO, ::MIME"text/markdown", b::BlockDescription)
rowwidth = max(maximum(ndigits, b.blkrows) + 1, 5)
colwidth = max(maximum(textwidth, b.blknms) + 1, 14)
function _markdown(b::BlockDescription)
ncols = length(b.blknms)
print(io, "|", rpad("rows", rowwidth), "|")
println(io, ("$(cpad(bn, colwidth))|" for bn in b.blknms)...)
print(io, "|", rpad(":", rowwidth, "-"), "|")
println(io, (":$("-"^(colwidth-2)):|" for _ in b.blknms)...)
align = repeat([:l], ncols+1)
newrow = ["rows"; [bn for bn in b.blknms] ]
rows = [newrow]

for (i, r) in enumerate(b.blkrows)
print(io, "|$(rpad(string(r), rowwidth))|")
newrow = [string(r)]
for j in 1:i
print(io, "$(rpad(b.ALtypes[i, j],colwidth))|")
push!(newrow, "$(b.ALtypes[i, j])")
end
i < ncols && print(io, "$(" "^colwidth)|"^(ncols-i))
println(io)
i < ncols && append!(newrow, repeat([""], ncols-i))
push!(rows, newrow)
end

tbl = Markdown.Table(rows, align)
return tbl
end

function Base.show(io::IO, ::MIME"text/markdown", lrt::LikelihoodRatioTest)
function _markdown( lrt::LikelihoodRatioTest)
Δdf = lrt.tests.dofdiff
Δdev = lrt.tests.deviancediff

Expand Down Expand Up @@ -52,16 +92,13 @@ function Base.show(io::IO, ::MIME"text/markdown", lrt::LikelihoodRatioTest)
end

tbl = Markdown.Table(outrows, [:l, :r, :r, :r, :r, :l])

show(io, Markdown.MD(tbl))
return tbl
end



_dname(::GeneralizedLinearMixedModel) = "Dispersion"
_dname(::LinearMixedModel) = "Residual"

function Base.show(io::IO, ::MIME"text/markdown", m::MixedModel)
function _markdown(m::MixedModel)
if m.optsum.feval < 0
@warn("Model has not been fit: results will be nonsense")
end
Expand All @@ -80,7 +117,7 @@ function Base.show(io::IO, ::MIME"text/markdown", m::MixedModel)
σwidth = _printdigits(σvec)

newrow = ["", "Est.", "SE", "z", "p"]
align = [:l, :l, :r, :r, :r]
align = [:l, :r, :r, :r, :r]

for rr in fnames(m)
push!(newrow,"σ_$(rr)")
Expand Down Expand Up @@ -113,11 +150,10 @@ function Base.show(io::IO, ::MIME"text/markdown", m::MixedModel)
end

tbl = Markdown.Table(rows, align)
show(io, Markdown.MD(tbl))
return tbl
end


function Base.show(io::IO, ::MIME"text/markdown", s::OptSummary)
function _markdown(s::OptSummary)
rows = [["", ""],

["**Initialization**", ""],
Expand All @@ -126,7 +162,7 @@ function Base.show(io::IO, ::MIME"text/markdown", s::OptSummary)

["**Optimizer settings** ", ""],
["Optimizer (from NLopt)", "`$(s.optimizer)`"],
["`Lower bounds`", string(s.lowerbd)],
["Lower bounds", string(s.lowerbd)],
["`ftol_rel`", string(s.ftol_rel)],
["`ftol_abs`", string(s.ftol_abs)],
["`xtol_rel`", string(s.xtol_rel)],
Expand All @@ -141,10 +177,10 @@ function Base.show(io::IO, ::MIME"text/markdown", s::OptSummary)
["Return code", "`$(s.returnvalue)`"]]

tbl = Markdown.Table(rows, [:l, :l])
show(io, Markdown.MD(tbl))
return tbl
end

function Base.show(io::IO, ::MIME"text/markdown", vc::VarCorr)
function _markdown(vc::VarCorr)
σρ = vc.σρ
nmvec = string.([keys(σρ)...])
cnmvec = string.(foldl(vcat, [keys(sig)...] for sig in getproperty.(values(σρ), )))
Expand Down Expand Up @@ -202,6 +238,7 @@ function Base.show(io::IO, ::MIME"text/markdown", vc::VarCorr)
append!(rr, repeat([" "], rowlen-length(rr)))
end
append!(align, repeat([:r], rowlen-length(align)))

tbl = Markdown.Table(rows, align)
show(io, Markdown.MD(tbl))
return tbl
end
138 changes: 0 additions & 138 deletions test/markdown.jl

This file was deleted.

Loading

0 comments on commit 160783d

Please sign in to comment.