From e802346d008dc7ead819a6c8d37731da0918703e Mon Sep 17 00:00:00 2001 From: "Tamas K. Papp" Date: Fri, 9 Feb 2018 14:43:10 +0100 Subject: [PATCH 1/5] Options cleanup. - intoduce a type alias, making code lines significantly shorter. - this also serves as a default constructor, since {Any, Any} are the default parameters - move all options-related code to a single file (no substantial changes otherwise) - add a few docstrings, especially @pgf --- src/PGFPlotsX.jl | 16 +----- src/axiselements.jl | 26 +++++---- src/axislike.jl | 24 ++++----- src/options.jl | 121 ++++++++++++++++++++++++++++++++++++++++++ src/tikzdocument.jl | 2 +- src/tikzpicture.jl | 4 +- src/utilities.jl | 74 -------------------------- test/runtests.jl | 1 - test/test_elements.jl | 12 ++--- test/test_macros.jl | 2 +- 10 files changed, 155 insertions(+), 127 deletions(-) create mode 100644 src/options.jl diff --git a/src/PGFPlotsX.jl b/src/PGFPlotsX.jl index ed692940..0bec51a3 100644 --- a/src/PGFPlotsX.jl +++ b/src/PGFPlotsX.jl @@ -14,7 +14,6 @@ using Requires const DEBUG = haskey(ENV, "PGFPLOTSX_DEBUG") const CUSTOM_PREAMBLE_PATH = joinpath(@__DIR__, "..", "deps", "custom_preamble.tex") -const PGFOption = Union{Pair, String, OrderedDict} const AbstractDict = Union{Dict, OrderedDict} if !isfile(joinpath(@__DIR__, "..", "deps", "deps.jl")) @@ -25,20 +24,7 @@ include("../deps/deps.jl") print_tex(io::IO, a, b) = print_tex(io, a) print_tex(a) = print_tex(STDOUT, a) -# TODO: Make OptionType a trait somehow? -abstract type OptionType end - -Base.getindex(a::OptionType, s::String) = a.options[s] -Base.setindex!(a::OptionType, v, s::String) = (a.options[s] = v; a) -Base.delete!(a::OptionType, s::String) = (delete!(a.options, s); a) -Base.copy(a::OptionType) = deepcopy(a) -function Base.merge!(a::OptionType, d::OrderedDict) - for (k, v) in d - a[k] = v - end - return a -end - +include("options.jl") include("utilities.jl") function print_tex(io_main::IO, str::AbstractString) diff --git a/src/axiselements.jl b/src/axiselements.jl index 3661636b..248bbfef 100644 --- a/src/axiselements.jl +++ b/src/axiselements.jl @@ -4,7 +4,7 @@ struct Plot <: AxisElement elements::AbstractVector{Any} - options::OrderedDict{Any, Any} + options::Options label incremental::Bool _3d::Bool @@ -21,10 +21,10 @@ function Plot3(element::Vector, args::Vararg{PGFOption}; incremental = true, lab Plot(element, dictify(args), label, incremental, true) end -Plot(options::OrderedDict, element; kwargs...) = Plot(element, options; kwargs...) +Plot(options::Options, element; kwargs...) = Plot(element, options; kwargs...) Plot(element, args...; kwargs...) = Plot([element], args...; kwargs...) Plot3(element, args...; kwargs...) = Plot3([element], args...; kwargs...) -Plot3(options::OrderedDict, element; kwargs...) = Plot3(element, options; kwargs...) +Plot3(options::Options, element; kwargs...) = Plot3(element, options; kwargs...) function save(filename::String, plot::Plot; kwargs...) save(filename, Axis(plot); kwargs...) @@ -325,11 +325,11 @@ expand_scanlines(v::Vector{Int}, _) = v expand_scanlines(itr, _) = collect(Int, itr) struct Table <: OptionType - options::OrderedDict{Any, Any} + options::Options data::AbstractMatrix colnames::Union{Void, Vector{String}} scanlines::AbstractVector{Int} - function Table(options::OrderedDict{Any, Any}, data::AbstractMatrix, + function Table(options::Options, data::AbstractMatrix, colnames::Union{Void, Vector{String}}, scanlines::AbstractVector{Int}) nrow, ncol = size(data) @@ -356,8 +356,7 @@ this can be used for skipping coordinates or implicitly defining the dimensions of a matrix for `surf` and `mesh` plots. They are expanded using [`expand_scanlines`](@ref). """ -function Table(options::OrderedDict{Any, Any}, data::AbstractMatrix, - colnames, scanlines) +function Table(options::Options, data::AbstractMatrix, colnames, scanlines) Table(options, data, colnames ≡ nothing ? colnames : collect(String, colnames), expand_scanlines(scanlines, size(data, 1))) @@ -444,11 +443,11 @@ end [`table_fields`](@ref) is used to convert the arguments after options. See its methods for possible conversions. """ -Table(options::OrderedDict{Any, Any}, args...; kwargs...) = +Table(options::Options, args...; kwargs...) = Table(options, table_fields(args...; kwargs...)...) Table(args...; kwargs...) = - Table(OrderedDict{Any, Any}(), table_fields(args...; kwargs...)...) + Table(Options(), table_fields(args...; kwargs...)...) function print_tex(io_main::IO, table::Table) @unpack options, data, colnames, scanlines = table @@ -489,7 +488,7 @@ Placeholder for a table for which data is read directly from a file. Use the [`Table`](@ref) constructor. """ struct TableFile <: OptionType - options::OrderedDict{Any, Any} + options::Options path::AbstractString end @@ -501,10 +500,9 @@ accepted format. If you don't use an absolute path, it will be converted to one. """ -Table(options::OrderedDict{Any, Any}, path::AbstractString) = - TableFile(options, abspath(path)) +Table(options::Options, path::AbstractString) = TableFile(options, abspath(path)) -Table(path::AbstractString) = Table(OrderedDict{Any, Any}(), path) +Table(path::AbstractString) = Table(Options(), path) function print_tex(io_main::IO, tablefile::TableFile) @unpack options, path = tablefile @@ -521,7 +519,7 @@ end struct Graphics <: OptionType filename::String - options::OrderedDict{Any, Any} + options::Options end function Graphics(filename::String, args::Vararg{PGFOption}) diff --git a/src/axislike.jl b/src/axislike.jl index cb62ebdb..de236530 100644 --- a/src/axislike.jl +++ b/src/axislike.jl @@ -9,7 +9,7 @@ function (T::Type{<:AxisLike})(plots::AbstractVector, args::Vararg{PGFOption}) end (T::Type{<:AxisLike})(plot, args::Vararg{PGFOption}) = T([plot], dictify(args)) -(T::Type{<:AxisLike})(options::OrderedDict, element) = T(element, options) +(T::Type{<:AxisLike})(options::Options, element) = T(element, options) function (T::Type{<:AxisLike})(args::Vararg{PGFOption}) T([], args...) @@ -42,10 +42,10 @@ _in_between(::Any, ::Any) = "" struct Axis <: AxisLike plots::Vector{Any} - options::OrderedDict{Any, Any} + options::Options # get rid of default constructor or ambiguities - Axis(v::Vector, o::OrderedDict{Any, Any}) = new(v, o) + Axis(v::Vector, o::Options) = new(v, o) end _tex_name(::Axis) = "axis" @@ -56,12 +56,12 @@ _tex_name(::Axis) = "axis" struct GroupPlot <: AxisLike plots::Vector{Any} - axisoptions::Vector{OrderedDict{Any, Any}} - options::OrderedDict{Any, Any} - # nextgroupplot::Vector{OrderedDict{Any, Any}} # options for \nextgroupplot + axisoptions::Vector{Options} + options::Options + # nextgroupplot::Vector{Options} # options for \nextgroupplot # get rid of default constructor or ambiguities - GroupPlot(v::Vector, o::OrderedDict{Any, Any}) = new(convert(Vector{Any}, v), [OrderedDict() for i in 1:length(v)], o) - GroupPlot(o::OrderedDict{Any, Any}) = new(Any[], OrderedDict{Any, Any}[], o) + GroupPlot(v::Vector, o::Options) = new(convert(Vector{Any}, v), [Options() for i in 1:length(v)], o) + GroupPlot(o::Options) = new(Any[], Options[], o) end function print_tex(io::IO, v::Vector, gp::GroupPlot) @@ -70,7 +70,7 @@ function print_tex(io::IO, v::Vector, gp::GroupPlot) end end -Base.push!(gp::GroupPlot, plot) = (push!(gp.plots, plot); push!(gp.axisoptions, OrderedDict()); gp) +Base.push!(gp::GroupPlot, plot) = (push!(gp.plots, plot); push!(gp.axisoptions, Options()); gp) Base.push!(gp::GroupPlot, plot, args...) = (push!(gp.plots, plot); push!(gp.axisoptions, dictify(args)); gp) _tex_name(::GroupPlot) = "groupplot" @@ -88,10 +88,10 @@ end struct PolarAxis <: AxisLike plots::Vector{Any} - options::OrderedDict{Any, Any} - # nextgroupplot::Vector{OrderedDict{Any, Any}} # options for \nextgroupplot + options::Options + # nextgroupplot::Vector{Options} # options for \nextgroupplot # get rid of default constructor or ambiguities - PolarAxis(v::Vector, o::OrderedDict{Any, Any}) = new(convert(Vector{Any}, v), o) + PolarAxis(v::Vector, o::Options) = new(convert(Vector{Any}, v), o) end _tex_name(::PolarAxis) = "polaraxis" diff --git a/src/options.jl b/src/options.jl new file mode 100644 index 00000000..00fdb77b --- /dev/null +++ b/src/options.jl @@ -0,0 +1,121 @@ +""" +Options passed to `pgfplots` for various structures (`table`, `plot`, etc). + +Contents emitted in `key = value` form, or `key` when `value ≡ nothing`. Also +see the [`@pgf`](@ref) convenience macro. +""" +const Options = OrderedDict + +function prockey(key) + if isa(key, Symbol) || isa(key, String) + return :($(string(key)) => nothing) + elseif @capture(key, (a_ : b_) | (a_ => b_) | (a_ = b_)) + return :($(string(a))=>$b) + end + error("Invalid pgf option $key") +end + +function procmap(d) + if @capture(d, f_(xs__)) + return :($f($(map(procmap, xs)...))) + elseif !@capture(d, {xs__}) + return d + else + return :($(Options)($(map(prockey, xs)...))) + end +end + +""" + @pgf { ... } + + @pgf some(nested(form({ ... }))) + +Construct [`Options`](@ref) from comma-delimited `key` (without value), +`key = value`, `key : value`, or `key => value` pairs enclosed in `{ ... }`, +anywhere in the expression. + +Multi-word keys need to be quoted. + +```julia +@pgf { + "only marks", + mark = "o", + color => "black", +} +``` +""" +macro pgf(ex) + esc(prewalk(procmap, ex)) +end + +""" +Types also accepted as options. +""" +const PGFOption = Union{Pair, String, Options} + +# TODO: Make OptionType a trait somehow? +""" +Subtypes have an `options::Options` field. +""" +abstract type OptionType end + +Base.getindex(a::OptionType, s::String) = a.options[s] +Base.setindex!(a::OptionType, v, s::String) = (a.options[s] = v; a) +Base.delete!(a::OptionType, s::String) = (delete!(a.options, s); a) +Base.copy(a::OptionType) = deepcopy(a) +function Base.merge!(a::OptionType, d::Options) + for (k, v) in d + a[k] = v + end + return a +end + +function print_options(io::IO, options::Options) + print(io, "[") + print_opt(io, options) + print(io, "]\n") +end + +accum_opt!(d::AbstractDict, opt::String) = d[opt] = nothing +accum_opt!(d::AbstractDict, opt::Pair) = d[first(opt)] = last(opt) +function accum_opt!(d::AbstractDict, opt::AbstractDict) + for (k, v) in opt + d[k] = v + end +end + +function dictify(args) + d = Options() + for arg in args + accum_opt!(d, arg) + end + return d +end + +function print_opt(io::IO, d::AbstractDict) + replace_underline(x) = x + replace_underline(x::Union{String, Symbol}) = replace(string(x), "_", " ") + for (i, (k, v)) in enumerate(d) + print(io, replace_underline(k)) + if v != nothing + print(io, "={") + print_opt(io, v) + print(io, "}") + end + if i != length(d) + print(io, ", ") + end + end +end + +print_opt(io::IO, s) = print(io, s) +print_opt(io::IO, v::Vector) = print(io, join(v, ",")) + +function print_opt(io::IO, t::Tuple) + length(t) == 0 && return + for i in 1:length(t) + i != 1 && print(io, "{") + print_opt(io, t[i]) + i != length(t) && print(io, "}") + end +end diff --git a/src/tikzdocument.jl b/src/tikzdocument.jl index 3deba8ce..56185c87 100644 --- a/src/tikzdocument.jl +++ b/src/tikzdocument.jl @@ -13,7 +13,7 @@ end TikzDocument(; preamble = String[]) = TikzDocument([], preamble) TikzDocument(element::Vector) = TikzDocument(element, String[]) TikzDocument(element, args...) = TikzDocument([element], args...) -TikzDocument(options::OrderedDict, element) = TikzDocument(element, options) +TikzDocument(options::Options, element) = TikzDocument(element, options) Base.push!(td::TikzDocument, v) = (push!(td.elements, v); td) push_preamble!(td::TikzDocument, v) = (push!(td.preamble, v); td) diff --git a/src/tikzpicture.jl b/src/tikzpicture.jl index 0711ad07..ffed66c0 100644 --- a/src/tikzpicture.jl +++ b/src/tikzpicture.jl @@ -2,7 +2,7 @@ const TikzElementOrStr = Union{TikzElement, String} struct TikzPicture <: OptionType elements::Vector{TikzElementOrStr} # Plots, nodes etc - options::OrderedDict{Any, Any} + options::Options end function TikzPicture(options::Vararg{PGFOption}) @@ -10,7 +10,7 @@ function TikzPicture(options::Vararg{PGFOption}) end TikzPicture(element::TikzElementOrStr, args...) = TikzPicture([element], args...) -TikzPicture(options::OrderedDict, element::TikzElementOrStr) = TikzPicture(element, options) +TikzPicture(options::Options, element::TikzElementOrStr) = TikzPicture(element, options) function TikzPicture(elements::Vector, options::Vararg{PGFOption}) TikzPicture(convert(Vector{TikzElementOrStr}, elements), dictify(options)) diff --git a/src/utilities.jl b/src/utilities.jl index ffcbd037..2ecfc587 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,34 +1,3 @@ -function prockey(key) - if isa(key, Symbol) || isa(key, String) - return :($(string(key)) => nothing) - elseif @capture(key, (a_ : b_) | (a_ => b_) | (a_ = b_)) - return :($(string(a))=>$b) - end - error("Invalid pgf option $key") -end - -function procmap(d) - if @capture(d, f_(xs__)) - return :($f($(map(procmap, xs)...))) - elseif !@capture(d, {xs__}) - return d - else - return :($(OrderedDict{Any, Any})($(map(prockey, xs)...))) - end -end - -macro pgf(ex) - esc(prewalk(procmap, ex)) -end - -function dictify(args) - d = OrderedDict{Any, Any}() - for arg in args - accum_opt!(d, arg) - end - return d -end - function print_indent(io::IO, str::String) for line in split(str, "\n") println(io, " ", line) @@ -41,49 +10,6 @@ function print_indent(f, io_main::IO) print_indent(io_main, String(take!(io))) end - -function print_options(io::IO, options::OrderedDict{Any, Any}) - print(io, "[") - print_opt(io, options) - print(io, "]\n") -end - -accum_opt!(d::AbstractDict, opt::String) = d[opt] = nothing -accum_opt!(d::AbstractDict, opt::Pair) = d[first(opt)] = last(opt) -function accum_opt!(d::AbstractDict, opt::AbstractDict) - for (k, v) in opt - d[k] = v - end -end - -function print_opt(io::IO, d::AbstractDict) - replace_underline(x) = x - replace_underline(x::Union{String, Symbol}) = replace(string(x), "_", " ") - for (i, (k, v)) in enumerate(d) - print(io, replace_underline(k)) - if v != nothing - print(io, "={") - print_opt(io, v) - print(io, "}") - end - if i != length(d) - print(io, ", ") - end - end -end - -print_opt(io::IO, s) = print(io, s) -print_opt(io::IO, v::Vector) = print(io, join(v, ",")) - -function print_opt(io::IO, t::Tuple) - length(t) == 0 && return - for i in 1:length(t) - i != 1 && print(io, "{") - print_opt(io, t[i]) - i != length(t) && print(io, "}") - end -end - """ $SIGNATURES diff --git a/test/runtests.jl b/test/runtests.jl index 64be87d1..60080324 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,7 +4,6 @@ using Colors using Compat using Contour using DataFrames -using DataStructures: OrderedDict using LaTeXStrings using RDatasets diff --git a/test/test_elements.jl b/test/test_elements.jl index 4c5fb320..18314e89 100644 --- a/test/test_elements.jl +++ b/test/test_elements.jl @@ -87,10 +87,8 @@ end @testset "tables" begin # compare results to these using ≅, defined above - table_named_noopt = pgf.Table(OrderedDict{Any, Any}(), hcat(1:10, 11:20), - ["a", "b"], Int[]) - table_unnamed_noopt = pgf.Table(OrderedDict{Any, Any}(), hcat(1:10, 11:20), - nothing, Int[]) + table_named_noopt = pgf.Table(pgf.Options(), hcat(1:10, 11:20), ["a", "b"], Int[]) + table_unnamed_noopt = pgf.Table(pgf.Options(), hcat(1:10, 11:20), nothing, Int[]) opt = pgf.@pgf { meaningless = "option" } table_named_opt = pgf.Table(opt, hcat(1:10, 11:20), ["a", "b"], Int[]) @@ -112,19 +110,19 @@ end # matrix and edges let x = randn(10), y = randn(5), z = cos.(x .+ y') - @test pgf.Table(x, y, z) ≅ pgf.Table(OrderedDict{Any, Any}(), + @test pgf.Table(x, y, z) ≅ pgf.Table(pgf.Options(), hcat(pgf.matrix_xyz(x, y, z)...), ["x", "y", "z"], 10) end # dataframe @test pgf.Table(DataFrame(a = 1:5, b = 6:10)) ≅ - pgf.Table(OrderedDict{Any, Any}(), hcat(1:5, 6:10), ["a", "b"], 0) + pgf.Table(pgf.Options(), hcat(1:5, 6:10), ["a", "b"], 0) # can't determine if it is named or unnamed @test_throws ArgumentError pgf.Table([1:10, :a => 11:20]) - @test squashed_repr_tex(pgf.Table(OrderedDict{Any, Any}(), + @test squashed_repr_tex(pgf.Table(pgf.Options(), [1 NaN; -Inf 4.0], ["xx", "yy"], diff --git a/test/test_macros.jl b/test/test_macros.jl index c6ae4f70..240767c6 100644 --- a/test/test_macros.jl +++ b/test/test_macros.jl @@ -13,7 +13,7 @@ end # test the @pgf macro -od(args...) = OrderedDict{Any, Any}(args...) +od(args...) = pgf.Options(args...) @testset "pgf tests" begin a = 1 From 77db0d731199c578b831ebeb5a262484d4de9135 Mon Sep 17 00:00:00 2001 From: "Tamas K. Papp" Date: Sat, 10 Feb 2018 11:06:04 +0100 Subject: [PATCH 2/5] Mention underscores for multi-word options, add example. --- src/options.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/options.jl b/src/options.jl index 00fdb77b..ad5a1ca0 100644 --- a/src/options.jl +++ b/src/options.jl @@ -34,11 +34,13 @@ Construct [`Options`](@ref) from comma-delimited `key` (without value), `key = value`, `key : value`, or `key => value` pairs enclosed in `{ ... }`, anywhere in the expression. -Multi-word keys need to be quoted. +Multi-word keys need to be either quoted, or written with underscores replacing +spaces. ```julia @pgf { "only marks", + mark_size = "0.6pt", mark = "o", color => "black", } From e82f34a6437d65535d83a6b84727e1c7ccba4eb6 Mon Sep 17 00:00:00 2001 From: "Tamas K. Papp" Date: Tue, 13 Feb 2018 10:12:47 +0100 Subject: [PATCH 3/5] Made options concrete. --- src/options.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/options.jl b/src/options.jl index ad5a1ca0..131a2c17 100644 --- a/src/options.jl +++ b/src/options.jl @@ -4,7 +4,7 @@ Options passed to `pgfplots` for various structures (`table`, `plot`, etc). Contents emitted in `key = value` form, or `key` when `value ≡ nothing`. Also see the [`@pgf`](@ref) convenience macro. """ -const Options = OrderedDict +const Options = OrderedDict{Any, Any} function prockey(key) if isa(key, Symbol) || isa(key, String) From 51b760f1672b42c4c987e221a845df3b42762b3c Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Tue, 13 Feb 2018 11:34:51 +0100 Subject: [PATCH 4/5] fix thingy --- test/test_macros.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_macros.jl b/test/test_macros.jl index 7ffb4717..125b9b57 100644 --- a/test/test_macros.jl +++ b/test/test_macros.jl @@ -13,7 +13,7 @@ end # test the @pgf macro -od(args...) = pgf.Options(args...) +od(args...) = PGFPlotsX.Options(args...) @testset "pgf tests" begin a = 1 From 79be4363b663cb8ad364300d3baf9d5e8fd8dc3c Mon Sep 17 00:00:00 2001 From: "Tamas K. Papp" Date: Tue, 13 Feb 2018 11:50:52 +0100 Subject: [PATCH 5/5] removed exta dot hopefully this runs now --- test/test_elements.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_elements.jl b/test/test_elements.jl index 80e36c0e..4d10bbc8 100644 --- a/test/test_elements.jl +++ b/test/test_elements.jl @@ -111,7 +111,7 @@ end # matrix and edges let x = randn(10), y = randn(5), z = cos.(x .+ y') @test Table(x, y, z) ≅ Table(PGFPlotsX.Options(), - hcat(PGFPlotsX..matrix_xyz(x, y, z)...), + hcat(PGFPlotsX.matrix_xyz(x, y, z)...), ["x", "y", "z"], 10) end