diff --git a/.gitignore b/.gitignore index 75a07b5e0..c181d9e3f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ docs/package-lock.json docs/src/changelog.md .vscode/settings.json /scratch +docs/cheatsheet.pdf \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index 36c67fd17..70cbe6f78 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -14,12 +14,14 @@ Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Shapefile = "8e980c4a-a4fe-5da2-b3a7-4b4b0353a2f4" +Typst_jll = "eb4b1da6-20f6-5c66-9826-fdb8ad410d0e" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" +[sources] +AlgebraOfGraphics = {path = ".."} + [compat] Documenter = "1" DocumenterVitepress = "0.2" - -[sources] -AlgebraOfGraphics = { path = ".." } \ No newline at end of file +Typst_jll = "0.14" diff --git a/docs/cheatsheet.jl b/docs/cheatsheet.jl new file mode 100644 index 000000000..000f104bd --- /dev/null +++ b/docs/cheatsheet.jl @@ -0,0 +1,309 @@ +using AlgebraOfGraphics +using CairoMakie +using Makie.Colors +using Typst_jll + +struct Typst + s::String +end + + +to_typst(io::IO, v::AbstractVector) = foreach(v) do el + to_typst(io, el) +end + +to_typst(io::IO, s::String) = print(io, s) # just simple, no escaping + +function assetname!(io, ext) + ref = get(io, :assetcounter, nothing) + i = ref[] + ref[] += 1 + path = "$i.$ext" + return path +end + +function to_typst(io::IO, x::Union{Makie.Figure, Makie.FigureAxisPlot, AlgebraOfGraphics.FigureGrid}) + p = assetname!(io, "pdf") + Makie.save(p, x) + return print(io, "#image($(repr(p)))") +end + +struct SVG + path::String +end + +function to_typst(io::IO, s::SVG) + p = assetname!(io, "svg") + cp(s.path, p) + return print(io, "#image($(repr(p)))") +end + +""" + render(x; pdf_path, png_path) + +Render the cheatsheet document `x` to PDF and optionally PNG. +If `pdf_path` is provided, the PDF will be saved there. +If `png_path` is provided, a PNG preview will be saved there. +""" +function render(x; pdf_path = joinpath(@__DIR__, "cheatsheet.pdf"), png_path = nothing) + assetcounter = Ref(0) + return mktempdir() do dir + cd(dir) do + open("script.typ", "w") do io + to_typst(IOContext(io, :assetcounter => assetcounter), x) + end + fontpath = Makie.assetpath("fonts") + run(`$(typst()) compile script.typ --font-path $fontpath`) + if png_path !== nothing + run(`$(typst()) compile script.typ script.png --font-path $fontpath --ppi 150`) + end + end + cp(joinpath(dir, "script.pdf"), pdf_path, force = true) + if png_path !== nothing + cp(joinpath(dir, "script.png"), png_path, force = true) + end + end +end + +makieyellow = colorant"#e8cb26" +makieblue = colorant"#3182bb" +makiered = colorant"#dd3366" + +set_theme!( + Axis = (; + width = 35, + height = 35, + xticksvisible = false, + yticksvisible = false, + xticklabelsvisible = false, + yticklabelsvisible = false, + xautolimitmargin = (0.15, 0.15), + yautolimitmargin = (0.15, 0.15), + xlabelpadding = 0, + ylabelpadding = 0, + titlegap = 1, + ), + Colorbar = (; + labelpadding = 0, + ticklabelsvisible = false, + ticksvisible = false, + spinewidth = 0, + width = 5, + ), + Legend = (; + rowgap = 0, + colgap = 0, + padding = 1, + patchlabelgap = 3, + # nbanks = 2, + titlegap = -2, + patchsize = (6, 6), + framevisible = false, + tellheight = true, + ), + colgap = 3, + rowgap = 3, + fontsize = 6 / 0.75, + figure_padding = (2, 2, 2, 2), + backgroundcolor = :transparent, + linecolor = makiered, + markercolor = makiered, + markersize = 8, + patchcolor = makiered, + colormap = [makieblue, "gray90"], + palettes = (; + color = [makiered, makieblue, makieyellow], + ), +) + +macro vec(x) + x isa String && return x + @assert x isa Expr && x.head === :string + return esc(Expr(:vect, x.args...)) +end + + +datasets = """ +df_one = (A=[1,4,6,8], B=[2,6,4,5], C=[3,2,1,0], D=["a","b","c","d"]) +df_two = (E=repeat(["e","f"],inner=50), F=[randn(50);randn(50).+3]) +df_three = (G=1:30, H=sin.(range(0,2pi,30)).+rand.(), I=cos.(range(0,2pi,30)).+rand.()) +df_four = (J=repeat(1:3,3), K=repeat(1:3,inner=3), L=[0,1,2,0.5,2,4,1,4,5]) +df_five = (M=repeat(1:3,3), N=[0,2,3,0.5,3.5,5,1,5,7], O=repeat(["g","h","i"],inner=3)) +df_six = (P=1:4, Q=["A","B","A","B"], R=4:-1:1, S=[0.5,0.6,0.3,0.7], T=[5.1,3.9,2.7,1.5], U=[1,1,2,2]) +""" + +eval(Meta.parseall(datasets)) + +function plottable(args...) + @assert iseven(length(args)) + v = Any["#table(columns: 2, stroke: none, inset: 0pt, column-gutter: 5pt"] + for (i, j) in Iterators.partition(args, 2) + push!( + v, ",[", + i, + "],table.cell(align: horizon, [", + j, + "])" + ) + end + push!(v, ")") + return v +end + +block(title, content) = [ + "#block(fill: oklch(97%, 0.02, 0deg), inset: 0pt, radius: 4pt, clip: true, [#block(fill: oklch(92%, 0.04, 0deg), width: 100%, inset: 4pt, sticky: true)[*$title*]\n#block(inset: 4pt)[", + content, + "]])", +] + +logo = SVG(joinpath(@__DIR__, "src", "assets", "logo_with_text.svg")) + +doc = [ + @vec( + """ + #set page(width: 297mm, height: 210mm, margin: 0.5cm) + #set text(font: "Helvetica", size: 7pt) + #show raw: set text(font: "Dejavu Sans Mono") + + #table( + columns: 3, + inset: 0pt, + column-gutter: (0pt, 10pt), + stroke: none, + align: horizon, + box([$(logo)], height: 7em), + table.cell( + text(size: 3em, fill: gray, weight: "bold", style: "italic")[Cheat Sheet], + ), + table.cell(block(raw($(repr(datasets))), fill: oklch(97%, 0.02, 270deg), inset: 4pt, radius: 4pt)), + ) + + #columns(4, gutter: 1em)[ + """ + ), + block( + "`data(df_one) * mapping(:A, :B)`", plottable( + data(df_one) * mapping(:A, :B) * visual(Scatter) |> draw, + "`* visual(Scatter)`", + data(df_one) * mapping(:A, :B, color = :C) * visual(Scatter) |> draw, + "`* mapping(color=:C)\n* visual(Scatter)`", + data(df_one) * mapping(:A, :B, color = :C) * visual(Scatter) |> draw(scales(Color = (; colormap = :plasma))), + "`* mapping(color=:C)\n* visual(Scatter) |>\ndraw(scales(Color=(;colormap=:plasma))`", + data(df_one) * mapping(:A, :B, color = :D) * visual(Scatter) |> draw, + "`* mapping(color=:D)\n* visual(Scatter)`", + data(df_one) * mapping(:A, :B, color = :D) * visual(Scatter) |> draw(scales(Color = (; palette = :Set1_5))), + "`* mapping(color=:D)\n* visual(Scatter) |>\ndraw(scales(Color=(;palette=:Set1_5))`", + data(df_one) * mapping(:A, :B) * visual(Lines) |> draw, + "`* visual(Lines)`", + data(df_one) * mapping(:A, :B) * visual(ScatterLines) |> draw, + "`* visual(ScatterLines)`", + data(df_one) * mapping(:A, :B) * visual(Stairs) |> draw, + "`* visual(Stairs)`", + data(df_one) * mapping(:D, :B) * visual(BarPlot) |> draw, + "`* visual(BarPlot)`", + data(df_one) * mapping(:D, :B) * visual(BarPlot, direction = :x) |> draw, + "`* visual(BarPlot,direction=:x)`", + data(df_one) * (mapping(:A, :B) * visual(Scatter, markersize = 5) + mapping(:A, :B, text = :D => verbatim) * visual(Makie.Text, align = (:center, :center))) |> draw, + "`* mapping(text=:D=>verbatim)\n* visual(Makie.Text)`", + data(df_one) * (mapping(:A, :B) * visual(Scatter, markersize = 5) + mapping(:A, :B, text = :D => verbatim) * visual(Annotation, color = :black)) |> draw, + "`* mapping(text=:D=>verbatim)\n* visual(Annotation)`", + ) + ), + block( + "`data(df_one) * mapping(:A)`", plottable( + data(df_one) * mapping(:A) * visual(HLines) |> draw, + "`* visual(HLines)`", + data(df_one) * mapping(:A) * visual(VLines) |> draw, + "`* visual(VLines)`", + ) + ), + block( + "`data(df_two) * mapping(:E, :F)`", plottable( + data(df_two) * mapping(:E, :F) * visual(Violin) |> draw, + "`* visual(Violin)`", + data(df_two) * mapping(:E, :F) * visual(Violin, orientation = :horizontal) |> draw, + "`* visual(Violin,orientation=:horizontal)`", + data(df_two) * mapping(:E, :F) * visual(BoxPlot, strokecolor = makiered, color = :white, strokewidth = 1, outliercolor = makiered, markersize = 5) |> draw, + "`* visual(BoxPlot)`", + data(df_two) * mapping(:E, :F) * visual(BoxPlot, orientation = :horizontal, strokecolor = makiered, color = :white, strokewidth = 1, outliercolor = makiered, markersize = 5) |> draw, + "`* visual(BoxPlot,orientation=:horizontal)`", + ) + ), + block( + "`data(df_two) * mapping(:F)`", plottable( + data(df_two) * mapping(:F) * histogram() |> draw, + "`* histogram()`", + data(df_two) * mapping(:F) * AlgebraOfGraphics.density() |> draw, + "`* AoG.density()`", + data(df_two) * mapping(:F) * visual(QQNorm, markersize = 3, qqline = :fit) |> draw, + "`* visual(QQNorm)`", + ) + ), + block( + "`data(df_three) * mapping(:G, :H)`", plottable( + data(df_three) * mapping(:G, :H) * (visual(Scatter, markersize = 2) + smooth()) |> draw, + "`* smooth()`", + data(df_three) * mapping(:G, :H) * (visual(Scatter, markersize = 2) + linear()) |> draw, + "`* linear()`", + data(df_three) * mapping(:H, :I) * (AlgebraOfGraphics.density() + visual(Scatter, markersize = 2)) |> draw, + "`* AoG.density()`", + ) + ), + block( + "`data(df_four) * mapping(:J, :K, :L)`", plottable( + data(df_four) * mapping(:J, :K, :L) * visual(Heatmap) |> draw, + "`* visual(Heatmap)`", + data(df_four) * mapping(:J, :K, :L) * contours(levels = 5) |> draw, + "`* contours(bands=4)`", + data(df_four) * mapping(:J, :K, :L) * filled_contours(bands = 4) |> draw, + "`* filled_contours(bands=4)`", + ) + ), + block( + "`data(df_five) * mapping(:M, :N)`", plottable( + data(df_five) * mapping(:M, :N, group = :O) * visual(Lines) |> draw, + "`* mapping(group=:O)\n* visual(Lines)`", + data(df_five) * mapping(:M, :N, color = :O) * visual(Lines) |> draw, + "`* mapping(color=:O)\n* visual(Lines)`", + data(df_five) * mapping(:M, :N, linestyle = :O) * visual(Lines) |> draw, + "`* mapping(linestyle=:O)\n* visual(Lines)`", + data(df_five) * mapping(:M, :N, marker = :O) * visual(ScatterLines) |> draw, + "`* mapping(marker=:O)\n* visual(ScatterLines)`", + data(df_five) * mapping(:M, :N, color = :O, dodge = :O) * visual(BarPlot) |> draw, + "`* mapping(color=:O,dodge=:O)\n* visual(BarPlot)`", + data(df_five) * mapping(:M, :N, color = :O, stack = :O) * visual(BarPlot) |> draw, + "`* mapping(color=:O,stack=:O)\n* visual(BarPlot)`", + data(df_five) * mapping(:M, :N, row = :O) * visual(Lines) |> draw(axis = (; width = 30, height = 15)), + "`* mapping(row=:O)\n* visual(Lines)`", + data(df_five) * mapping(:M, :N, col = :O) * visual(Lines) |> draw(axis = (; width = 15, height = 30)), + "`* mapping(col=:O)\n* visual(Lines)`", + data(df_five) * mapping(:M, :N, layout = :O) * visual(Lines) |> draw(axis = (; width = 20, height = 20)), + "`* mapping(layout=:O)\n* visual(Lines)`", + ) + ), + block( + "`data(df_six)`", plottable( + data(df_six) * (mapping(:P, :R) * visual(BarPlot) + mapping(:P, :R, :S) * visual(Errorbars, color = :black)) |> draw, + "`* (mapping(:P,:R) * visual(BarPlot)\n+ mapping(:P,:R,:S) * visual(Errorbars))`", + data(df_six) * (mapping(:P, :R) * visual(BarPlot) + mapping(:P, :R, :S, :S => x -> 2x) * visual(Errorbars, color = :black)) |> draw, + "`* (mapping(:P,:R) * visual(BarPlot)\n+ mapping(:P,:R,:S,:S=>x->2x) * visual(Errorbars))`", + data(df_six) * (mapping(:P, :R) * visual(BarPlot) + mapping(:P, :S, :T) * visual(Rangebars, color = :black)) |> draw, + "`* mapping(group=:O)\n* visual(Lines)`", + data(df_six) * (mapping(:U, :R, dodge = :Q, color = :Q) * visual(BarPlot) + mapping(:U, :R, :S, dodge_x = :Q) * visual(Errorbars, color = :black)) |> draw, + "`* (mapping(:U,:R,dodge=:Q,color=:Q) * visual(BarPlot)\n+ mapping(:U,:R,:S,dodge_x=:Q) * visual(Errorbars))`", + ) + ), + block( + "Others", plottable( + data(df_one) * mapping(:A, :B) * visual(Scatter) + mapping(2, 0.4) * visual(ABLines) |> draw, + "`data(df_one) * mapping(:A,:B) * visual(Scatter) + mapping(2,0.4) * visual(ABLines)`", + data(df_one) * mapping(:A, :B) * visual(Scatter) + mapping(-5, -17, 4, 6) * visual(Annotation, color = :black, text = "Here", shrink = (0, 5), style = Ann.Styles.LineArrow(head = Ann.Arrows.Line(length = 3))) |> draw, + "`mapping(-5,-17,4,6) * visual(Annotation, text=\"Here\", style=Ann.Styles.LineArrow())`", + ) + ), + "] // columns", +] + +function build_cheatsheet(; pdf_path = joinpath(@__DIR__, "cheatsheet.pdf"), png_path = nothing) + return render(doc; pdf_path, png_path) +end diff --git a/docs/make.jl b/docs/make.jl index 9cc3e6447..6645846bf 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -8,6 +8,18 @@ DocMeta.setdocmeta!(AlgebraOfGraphics, :DocTestSetup, :(using AlgebraOfGraphics) cp(joinpath(@__DIR__, "..", "CHANGELOG.md"), joinpath(@__DIR__, "src", "changelog.md"), force = true) +module Cheatsheet + @info "Building cheatsheet..." + # VitePress serves files from the public folder at the site root + publicpath = joinpath(@__DIR__, "src", "public") + mkpath(publicpath) + pdf_path = joinpath(publicpath, "cheatsheet.pdf") + png_path = joinpath(publicpath, "cheatsheet.png") + include(joinpath(@__DIR__, "cheatsheet.jl")) + build_cheatsheet(; pdf_path, png_path) + @info "Cheatsheet built successfully" +end + makedocs(; modules = [AlgebraOfGraphics], authors = "Pietro Vertechi", diff --git a/docs/src/index.md b/docs/src/index.md index 365e4e58a..ac1f45ffd 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -24,6 +24,15 @@ hero: AlgebraOfGraphics (AoG) defines a language for data visualization, inspired by the grammar-of-graphics system made popular by the R library [ggplot2](https://ggplot2.tidyverse.org/). It is based on the plotting package [Makie.jl](https://docs.makie.org/stable/) which means that most capabilities of Makie are available, and AoG plots can be freely composed with normal Makie figures. +## Cheat Sheet + +````@raw html + + AlgebraOfGraphics Cheat Sheet + +

📄 Download Cheat Sheet (PDF)

+```` + ## Example In AlgebraOfGraphics, a few simple building blocks can be combined using `+` and `*` to quickly create complex visualizations, like this: