Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a Documenter @figure block #12

Merged
merged 6 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions docs/documenter_figure_block.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#=

# Figure blocks

The figure block is just a rejiggered example block, with the MIME-type set

=#

"""
abstract type FigureBlocks

This type encodes a block denoted by `@figure`.
"""
abstract type FigureBlocks <: Documenter.Expanders.NestedExpanderPipeline end


Documenter.Selectors.order(::Type{FigureBlocks}) = 8.0 # like @example
Documenter.Selectors.matcher(::Type{FigureBlocks}, node, page, doc) = Documenter.iscode(node, r"^@figure")

# TODO: we have to have a better / less fragile way to do this than plain text references!
module MakieDocsHelpers4
struct AsMIME{M<:MIME,V}
mime::M
value::V
end

Base.show(io::IO, m::MIME, a::AsMIME{MIME}) where MIME = show(io, m, a.value)
end

function Documenter.Selectors.runner(::Type{FigureBlocks}, node, page, doc)
el = node.element
infoexpr = Meta.parse(el.info)
args = infoexpr.args[3:end]
if !isempty(args) && args[1] isa Symbol
blockname = string(args[1])
kwargs = args[2:end]
else
blockname = ""
kwargs = args
end
kwargs = Dict(map(kwargs) do expr
if !(expr isa Expr) && expr.head !== :(=) && length(expr.args) == 2 && expr.args[1] isa Symbol && expr.args[2] isa Union{String,Number,Symbol}
error("Invalid keyword arg expression: $expr")
end
expr.args[1] => expr.args[2]
end)
el.info = "@example $blockname"
el.code = transform_figure_code(el.code; kwargs...)
Documenter.Selectors.runner(Documenter.Expanders.ExampleBlocks, node, page, doc)
end

function _mime_from_format(fmt::String, backend::Symbol)
return if fmt in ("png", "jpeg") # these are supported by all backends!
"image/$fmt"
elseif fmt == "svg"
@assert backend == :CairoMakie "Only CairoMakie can emit `$fmt` files. Please either change the mime in your `@figure` block to a raster format like PNG, or change the backend to CairoMakie."
"image/svg+xml"
elseif fmt in ("pdf", "eps")
@assert backend == :CairoMakie "Only CairoMakie can emit `$fmt` files. Please either change the mime in your `@figure` block to a raster format like PNG, or change the backend to CairoMakie."
"application/$fmt"
elseif fmt == "html"
@assert backend == :WGLMakie "Only WGLMakie can emit `$fmt` files. Please either change the mime in your `@figure` block to a raster format like PNG, or change the backend to WGLMakie."
"text/html"
else
error("Unknown format `$fmt` detected.")
end
end

function transform_figure_code(code::String; backend::Symbol = :CairoMakie, type = "png", kwargs...)
backend in (:CairoMakie, :GLMakie, :WGLMakie, :RPRMakie) || error("Invalid backend $backend")
mimetype = _mime_from_format(type, backend)
# All this code is within the Documenter runner module's scope, so we have to go up one level to go to Main.
# I am wondering if, in theory, we should actually just access Main directly?
"""
import $backend # hide
$(backend).activate!() # hide
import Main.MakieDocsHelpers4 # hide
var"#result" = begin # hide
$code
end # hide
if var"#result" isa Makie.FigureLike # hide
MakieDocsHelpers4.AsMIME(MIME"$mimetype"(), var"#result") # hide
else # hide
var"#result" # hide
end # hide
"""
end
12 changes: 9 additions & 3 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ using SwarmMakie
using Documenter, DocumenterVitepress, Literate
using CairoMakie

CairoMakie.activate!(type="svg", pt_per_unit = 0.75)
CairoMakie.activate!(type="svg", pt_per_unit = 1)

include("documenter_figure_block.jl")

DocMeta.setdocmeta!(SwarmMakie, :DocTestSetup, :(using SwarmMakie); recursive=true)

Expand All @@ -28,6 +30,10 @@ function _add_meta_edit_link_generator(path)
end
end

function _replace_example_with_figure(input)
return replace(input, "```@example" => "```@figure")
end

# First letter of `str` is made uppercase and returned
ucfirst(str::String) = string(uppercase(str[1]), str[2:end])

Expand All @@ -43,7 +49,7 @@ function process_literate_recursive!(pages::Vector{Any}, path::String; source_pa
Literate.markdown(
path, output_dir;
flavor = Literate.CommonMarkFlavor(),
postprocess = _add_meta_edit_link_generator(joinpath(relpath(source_path, output_dir), relative_path))
postprocess = _add_meta_edit_link_generator(joinpath(relpath(source_path, output_dir), relative_path)) ∘ _replace_example_with_figure
)
push!(pages, joinpath("source", splitext(relative_path)[1] * ".md"))
end
Expand All @@ -59,7 +65,7 @@ withenv("JULIA_DEBUG" => "Literate") do # allow Literate debug output to escape
end

# As a special case, literatify the examples.jl file in docs/src to Documenter markdown
Literate.markdown(joinpath(@__DIR__, "src", "examples.jl"), joinpath(@__DIR__, "src"); flavor = Literate.DocumenterFlavor())
Literate.markdown(joinpath(@__DIR__, "src", "examples.jl"), joinpath(@__DIR__, "src"); flavor = Literate.DocumenterFlavor(), postprocess = _replace_example_with_figure)

makedocs(;
modules=[SwarmMakie],
Expand Down
2 changes: 1 addition & 1 deletion docs/src/algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ In addition, SwarmMakie offers jittered scatter plots as algorithms to `beeswarm

Here's a comparison of all the available algorithms:

```@example all_algorithms
```@figure all_algorithms
using SwarmMakie, CairoMakie
algorithms = [NoBeeswarm() SimpleBeeswarm() WilkinsonBeeswarm(); UniformJitter() PseudorandomJitter() QuasirandomJitter()]
fig = Figure(; size = (800, 450))
Expand Down
6 changes: 3 additions & 3 deletions docs/src/gutters.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Gutters

```@example gutters
```@figure gutters; backend=:CairoMakie; type="svg"
using SwarmMakie, CairoMakie
xs = rand(1:10, 2000)
beeswarm(xs, rand(2000); gutter = 0.3, color = xs)
Expand All @@ -14,7 +14,7 @@ A nice gutter size to avoid overlap in neighboring categories ranges between `0.

## Examples

```@example gutters
```@figure gutters
using SwarmMakie, CairoMakie
f, a, p = beeswarm(
rand(1:3, 300), randn(300);
Expand All @@ -23,7 +23,7 @@ f, a, p = beeswarm(
p.gutter = 0.5
```
Note the warning messages printed here! These can be helpful to diagnose when your data is moving too far out of the gutter, but you can turn them off by passing `gutter_threshold = false` or setting the `gutter_threshold` to a higher value (must be an `Int` and >0).
```@example gutters
```@figure gutters
f
```

Expand Down
4 changes: 2 additions & 2 deletions docs/src/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Being a Makie recipe, you can also use this with AlgebraOfGraphics.

Here's a quick example to get you started:

```@example quickstart
```@figure quickstart
using CairoMakie, SwarmMakie
xs = rand(1:3, 40)
ys = randn(40)
Expand All @@ -27,7 +27,7 @@ f

As a Makie recipe, `beeswarm` also composes with AlgebraOfGraphics!

```@example aog
```@figure aog
using AlgebraOfGraphics, CairoMakie, SwarmMakie
using RDatasets, DataFrames
iris = dataset("datasets", "iris")
Expand Down
Loading