From f689a007b7d0ae8f3323cebf393dea4d73b555a0 Mon Sep 17 00:00:00 2001 From: "Tamas K. Papp" Date: Mon, 31 Aug 2020 16:26:34 +0200 Subject: [PATCH] Make merge and merge! accept more than 2 arguments. (#184) * Make merge and merge! accept more than 2 arguments. Incidentally, - add unit tests for `merge` and `copy` for `Options` - allow `merge!` for options - add `merge` example to docs * Cleanup of options part of the manual. Also add examples for #183. --- docs/src/man/options.md | 75 ++++++++++++++++++++++++++++++++++------- src/options.jl | 17 ++++++---- test/test_options.jl | 22 ++++++++++++ 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/docs/src/man/options.md b/docs/src/man/options.md index 882e883d..79d983a3 100644 --- a/docs/src/man/options.md +++ b/docs/src/man/options.md @@ -103,7 +103,7 @@ Values should be valid Julia expressions, as they are evaluated, so you cannot u In addition to replacing underscores in keys, the following transformations of values are done when the options are written in `.tex` style: -* A list as a value is written as "comma joined" e.g. `[1, 2, 3] -> "1, 2, 3"`. +* A list as a value is written as “comma joined” e.g. `[1, 2, 3] -> "1, 2, 3"`. * A tuple as a value is written with braces delimiting the elements e.g. `(60, 30) -> {60}{30}` @@ -142,21 +142,17 @@ julia> print_tex(p) ; ``` -You can also merge in options that have been created separately, using `merge!`: +## Working with options -```jldoctest -julia> a = Axis(); +Collections of options are first-class objects: they can be used independently of `Plot`, `Axis`, and similar, copied, modified, and merged. -julia> @pgf opts = {xmin = 0, ymax = 1, ybar}; +This allows a disciplined approach to working with complex plots: for example, you can create a set of default options for some purpose (eg plots in a research paper, with a style imposed by a journal), and then modify this as needed for individual plots. It is then easy to apply, for example, a “theme” to an axis where the theme is a set of options already saved. -julia> merge!(a, opts); +Another use case is creating orthogonal sets of options, eg one for axis annotations and another one for legends, and merging these as necessary. -julia> print_tex(a) -\begin{axis}[xmin={0}, ymax={1}, ybar] -\end{axis} -``` +### Extending and combining options -An alternative to using `merge!` is using `...` to splice an option into another one, e.g. +Use `...` to splice an option into another one, e.g. ```jldoctest julia> theme = @pgf {xmajorgrids, ymajorgrids}; @@ -168,13 +164,66 @@ julia> a = Axis( julia> print_tex(a) \begin{axis}[xmajorgrids, ymajorgrids, title={Foo}] \end{axis} + +julia> print_tex(theme) # original is not modified +[xmajorgrids, ymajorgrids] +``` + +You can also `merge` sets of options: +```jldoctest +julia> O1 = @pgf { color = "red" }; + +julia> O2 = @pgf { dashed }; + +julia> O3 = @pgf { no_marks }; + +julia> print_tex(Plot(merge(O1, O2, O3), Table(1:2, 1:2))) +\addplot[color={red}, dashed, no marks] + table[row sep={\\}] + { + \\ + 1 1 \\ + 2 2 \\ + } + ; ``` +Again, the value of original options is unchanged above. + + +### Modifying options + +You can modify existing options with `push!`, `append!`, and `merge!`. The first two expect pairs of a string and a value (may be `nothing` for options like `"red"`), and are mostly useful when you are generating options using a function. `merge!` of course accepts options. -It is then easy to apply, for example, a “theme” to an axis where the theme is a set of options already saved. +```jldoctest +julia> opt = @pgf {}; + +julia> push!(opt, :color => "red", :mark => "x"); + +julia> append!(opt, [:style => "thick", :mark_options => @pgf { scale = 0.4 }]); + +julia> merge!(opt, @pgf { "error bars/y dir=both", "error bars/y explicit" }); + +julia> print_tex(opt) +[color={red}, mark={x}, style={thick}, mark options={scale={0.4}}, error bars/y dir=both, error bars/y explicit] +``` + +All containers with options also support using `merge!` directly. + +```jldoctest +julia> a = Axis(); + +julia> @pgf opts = {xmin = 0, ymax = 1, ybar}; + +julia> merge!(a, opts); + +julia> print_tex(a) +\begin{axis}[xmin={0}, ymax={1}, ybar] +\end{axis} +``` ## Empty options -Empty options are not printed by default, but printing `[]` can be useful in some cases, eg when combined with global settings `\pgfplotsset{every axis plot/.append style={...}}` in LaTeX code. In order to force printing empty options, it is recommended to use `{}` in expressions like +Empty options are not emitted by default, but using in LaTeX code `[]` can be useful in some cases, eg when combined with global settings `\pgfplotsset{every axis plot/.append style={...}}`. In order to force printing empty options, it is recommended to use `{}` in expressions like ```julia @pgf Plot({}, ...) diff --git a/src/options.jl b/src/options.jl index 81544466..f7a5317f 100644 --- a/src/options.jl +++ b/src/options.jl @@ -56,8 +56,11 @@ Base.haskey(o::Options, args...; kwargs...) = haskey(o.dict, args...; kwargs...) Base.copy(options::Options) = deepcopy(options) -Base.merge(a::Options, b::Options) = - Options(merge(a.dict, b.dict), a.print_empty || b.print_empty) +function Base.merge(options::Options, others::Options...) + args = (options, others...) + Options(mapreduce(opts -> opts.dict, merge, args), + mapreduce(opts -> opts.print_empty, |, args)) +end function prockey(key) if isa(key, Symbol) || isa(key, String) @@ -143,11 +146,13 @@ Base.delete!(o::OptionType, args...; kwargs...) = (delete!(o.options, args...; k Base.copy(a::OptionType) = deepcopy(a) -function Base.merge!(a::OptionType, options::Options) - for (k, v) in options.dict - a[k] = v +function Base.merge!(options::Union{Options,OptionType}, others::Options...) + for other in others + for (k, v) in other.dict + options[k] = v + end end - return a + options end """ diff --git a/test/test_options.jl b/test/test_options.jl index 95334929..e9a1b345 100644 --- a/test/test_options.jl +++ b/test/test_options.jl @@ -32,6 +32,28 @@ end "\\addplot[]\ntable[row sep={\\\\}]\n{\nx \\\\\n1 \\\\\n2 \\\\\n3 \\\\\n}\n;" # note [] end +@testset "operations on options" begin + O1 = @pgf { a = 1 } + + # copy + O2 = copy(O1) + @test O1 ≅ O2 + + # merge + @test merge(O1, @pgf { b = 2 }) ≅ @pgf { a = 1, b = 2 } + O3 = @pgf { a = 1, b = 2, c = 3 } + @test merge(O1, @pgf({ b = 2 }), @pgf({ c = 3 })) ≅ O3 + + # merge! + @test merge!(O1, @pgf({ b = 2 }), @pgf({ c = 3 })) ≡ O1 + @test O1 ≅ O3 + + # merge! for OptionType + P = Plot(O2, Table([1], [1])); # don't print, nonsensical + @test merge!(P, @pgf({ b = 2 }), @pgf({ c = 3 })) ≡ P + @test P.options ≅ O3 +end + @testset "options push! and append!" begin opt1 = "color" => "red" opt2 = "dashed"