From ed1c6e58dce3ae2bdd0dae0dc08e7c04ef610ef0 Mon Sep 17 00:00:00 2001 From: Isaac Wheeler Date: Tue, 27 May 2025 12:28:32 +0200 Subject: [PATCH 1/9] Get the change working --- PlotsBase/ext/UnitfulExt.jl | 16 ++++++++++------ PlotsBase/src/Commons/attrs.jl | 1 + PlotsBase/src/layouts.jl | 4 ++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/PlotsBase/ext/UnitfulExt.jl b/PlotsBase/ext/UnitfulExt.jl index a59d3981b..0169afe81 100644 --- a/PlotsBase/ext/UnitfulExt.jl +++ b/PlotsBase/ext/UnitfulExt.jl @@ -49,12 +49,16 @@ function fixaxis!(attr, x, axisletter) err = Symbol(axisletter, :error) # xerror, yerror, zerror axisunit = Symbol(axisletter, :unit) # xunit, yunit, zunit axis = Symbol(axisletter, :axis) # xaxis, yaxis, zaxis - u = pop!(attr, axisunit, _unit(eltype(x))) # get the unit + u = get!(attr, axisunit, _unit(eltype(x))) # get the unit # if the subplot already exists with data, get its unit sp = get(attr, :subplot, 1) if sp ≤ length(attr[:plot_object]) && attr[:plot_object].n > 0 label = attr[:plot_object][sp][axis][:guide] - u = getaxisunit(label) + spu = getaxisunit(attr[:plot_object][sp][axis]) + if !isnothing(spu) + u = spu + end + attr[axisunit] = u # update the unit in the attributes get!(attr, axislabel, label) # if label was not given as an argument, reuse end # fix the attributes: labels, lims, ticks, marker/line stuff, etc. @@ -295,9 +299,9 @@ format_unit_label(l, u, f::NTuple{3,Char}) = string(f[1], l, ' ', f[ format_unit_label(l, u, f::Bool) = f ? format_unit_label(l, u, :round) : format_unit_label(l, u, nothing) format_unit_label(l, u, f::Symbol) = format_unit_label(l, u, UNIT_FORMATS[f]) -getaxisunit(::AbstractString) = NoUnits -getaxisunit(s::UnitfulString) = s.unit -getaxisunit(a::Axis) = getaxisunit(a[:guide]) +getaxisunit(::Nothing) = nothing +getaxisunit(u) = u +getaxisunit(a::Axis) = getaxisunit(a[:unit]) #============== Fix annotations @@ -329,7 +333,7 @@ function PlotsBase.locate_annotation( rel::NTuple{N,<:MissingOrQuantity}, label, ) where {N} - units = getaxisunit(sp.attr[:xaxis], sp.attr[:yaxis], sp.attr[:zaxis]) + units = getaxisunit(sp.attr[:xaxis]), getaxisunit(sp.attr[:yaxis]), getaxisunit(sp.attr[:zaxis]) PlotsBase.locate_annotation(sp, _ustrip.(zip(units, rel)), label) end diff --git a/PlotsBase/src/Commons/attrs.jl b/PlotsBase/src/Commons/attrs.jl index 685473737..f0db8eca4 100644 --- a/PlotsBase/src/Commons/attrs.jl +++ b/PlotsBase/src/Commons/attrs.jl @@ -519,6 +519,7 @@ const _axis_defaults = KW( :widen => :auto, :draw_arrow => false, :unitformat => :round, + :unit => nothing, ) # add defaults for the letter versions diff --git a/PlotsBase/src/layouts.jl b/PlotsBase/src/layouts.jl index 231f9f150..26e95d8df 100644 --- a/PlotsBase/src/layouts.jl +++ b/PlotsBase/src/layouts.jl @@ -335,6 +335,9 @@ function link_axes!(axes::Axis...) a1 = axes[1] for i ∈ 2:length(axes) a2 = axes[i] + a1[:unit] ≡ a2[:unit] || error( + "Cannot link axes with different units: $(a1[:unit]) and $(a2[:unit])", + ) expand_extrema!(a1, Axes.ignorenan_extrema(a2)) for k ∈ (:extrema, :discrete_values, :continuous_values, :discrete_map) a2[k] = a1[k] @@ -422,6 +425,7 @@ function twin(sp, letter) tax[:grid] = false tax[:showaxis] = false tax[:ticks] = :none + tax[:unit] = orig_sp[get_attr_symbol(letter, :axis)][:unit] oax[:grid] = false oax[:mirror] = true twin_sp[:background_color_inside] = RGBA{Float64}(0, 0, 0, 0) From 1908c22401c268539c22de915476af2b02075feb Mon Sep 17 00:00:00 2001 From: Isaac Wheeler Date: Wed, 28 May 2025 13:29:01 +0200 Subject: [PATCH 2/9] Try out moving unitformat to axis guide set time --- PlotsBase/ext/UnitfulExt.jl | 116 +++++++++++++++--------------------- PlotsBase/src/Axes.jl | 28 +++++++++ 2 files changed, 75 insertions(+), 69 deletions(-) diff --git a/PlotsBase/ext/UnitfulExt.jl b/PlotsBase/ext/UnitfulExt.jl index 0169afe81..16ca28f2c 100644 --- a/PlotsBase/ext/UnitfulExt.jl +++ b/PlotsBase/ext/UnitfulExt.jl @@ -36,7 +36,7 @@ Main recipe if axisletter ≡ :z && get(plotattributes, :seriestype, :nothing) ∈ clims_types u = get(plotattributes, :zunit, _unit(eltype(x))) ustripattribute!(plotattributes, :clims, u) - append_unit_if_needed!(plotattributes, :colorbar_title, u) + # append_unit_if_needed!(plotattributes, :colorbar_title, u) end fixaxis!(plotattributes, x, axisletter) end @@ -44,8 +44,6 @@ end function fixaxis!(attr, x, axisletter) # Attribute keys axislabel = Symbol(axisletter, :guide) # xguide, yguide, zguide - axislims = Symbol(axisletter, :lims) # xlims, ylims, zlims - axisticks = Symbol(axisletter, :ticks) # xticks, yticks, zticks err = Symbol(axisletter, :error) # xerror, yerror, zerror axisunit = Symbol(axisletter, :unit) # xunit, yunit, zunit axis = Symbol(axisletter, :axis) # xaxis, yaxis, zaxis @@ -62,7 +60,7 @@ function fixaxis!(attr, x, axisletter) get!(attr, axislabel, label) # if label was not given as an argument, reuse end # fix the attributes: labels, lims, ticks, marker/line stuff, etc. - append_unit_if_needed!(attr, axislabel, u) + # append_unit_if_needed!(attr, axislabel, u) ustripattribute!(attr, err, u) if axisletter ≡ :y ustripattribute!(attr, :ribbon, u) @@ -80,7 +78,7 @@ end u = get(plotattributes, :zunit, _unit(eltype(z))) ustripattribute!(plotattributes, :clims, u) z = fixaxis!(plotattributes, z, :z) - append_unit_if_needed!(plotattributes, :colorbar_title, u) + # append_unit_if_needed!(plotattributes, :colorbar_title, u) x, y, z end @@ -176,10 +174,14 @@ end function fixmarkercolor!(attr) u = ustripattribute!(attr, :marker_z) ustripattribute!(attr, :clims, u) - u == NoUnits || append_unit_if_needed!(attr, :colorbar_title, u) + # u == NoUnits || append_unit_if_needed!(attr, :colorbar_title, u) +end +function fixlinecolor!(attr) + u = ustripattribute!(attr, :line_z) + ustripattribute!(attr, :clims, u) + # u == NoUnits || append_unit_if_needed!(attr, :colorbar_title, u) end fixmarkersize!(attr) = ustripattribute!(attr, :markersize) -fixlinecolor!(attr) = ustripattribute!(attr, :line_z) # strip unit from attribute[key] ustripattribute!(attr, key) = @@ -231,73 +233,49 @@ PlotsBase.protectedstring(s) = ProtectedString(s) Append unit to labels when appropriate =====================================# -append_unit_if_needed!(attr, key, u) = - append_unit_if_needed!(attr, key, get(attr, key, nothing), u) -# dispatch on the type of `label` -append_unit_if_needed!(attr, key, label::ProtectedString, u) = nothing -append_unit_if_needed!(attr, key, label::UnitfulString, u) = nothing -function append_unit_if_needed!(attr, key, label::Nothing, u) - attr[key] = if attr[:plot_object].backend == PlotsBase.backend_instance(:pgfplotsx) - UnitfulString(LaTeXString(Latexify.latexify(u)), u) - else - UnitfulString(string(u), u) - end -end -function append_unit_if_needed!(attr, key, label::S, u) where {S<:AbstractString} - isempty(label) && return attr[key] = UnitfulString(label, u) - attr[key] = if attr[:plot_object].backend == PlotsBase.backend_instance(:pgfplotsx) - UnitfulString( - LaTeXString( - format_unit_label( - label, - Latexify.latexify(u), - get(attr, Symbol(get(attr, :letter, ""), :unitformat), :round), - ), - ), - u, - ) - else - UnitfulString( - S( - format_unit_label( - label, - u, - get(attr, Symbol(get(attr, :letter, ""), :unitformat), :round), - ), - ), - u, - ) - end -end +# append_unit_if_needed!(attr, key, u) = +# append_unit_if_needed!(attr, key, get(attr, key, nothing), u) +# # dispatch on the type of `label` +# append_unit_if_needed!(attr, key, label::ProtectedString, u) = nothing +# append_unit_if_needed!(attr, key, label::UnitfulString, u) = nothing +# function append_unit_if_needed!(attr, key, label::Nothing, u) +# attr[key] = if PlotsBase.backend_name() ≡ :pgfplotsx +# UnitfulString(LaTeXString(Latexify.latexify(u)), u) +# else +# UnitfulString(string(u), u) +# end +# end +# function append_unit_if_needed!(attr, key, label::S, u) where {S<:AbstractString} +# isempty(label) && return attr[key] = UnitfulString(label, u) +# attr[key] = if PlotsBase.backend_name() ≡ :pgfplotsx +# UnitfulString( +# LaTeXString( +# format_unit_label( +# label, +# Latexify.latexify(u), +# get(attr, Symbol(get(attr, :letter, ""), :unitformat), :round), +# ), +# ), +# u, +# ) +# else +# UnitfulString( +# S( +# format_unit_label( +# label, +# u, +# get(attr, Symbol(get(attr, :letter, ""), :unitformat), :round), +# ), +# ), +# u, +# ) +# end +# end #============================================= Surround unit string with specified delimiters =============================================# -const UNIT_FORMATS = Dict( - :round => ('(', ')'), - :square => ('[', ']'), - :curly => ('{', '}'), - :angle => ('<', '>'), - :slash => '/', - :slashround => (" / (", ")"), - :slashsquare => (" / [", "]"), - :slashcurly => (" / {", "}"), - :slashangle => (" / <", ">"), - :verbose => " in units of ", - :none => nothing, -) - -format_unit_label(l, u, f::Nothing) = string(l, ' ', u) -format_unit_label(l, u, f::Function) = f(l, u) -format_unit_label(l, u, f::AbstractString) = string(l, f, u) -format_unit_label(l, u, f::NTuple{2,<:AbstractString}) = string(l, f[1], u, f[2]) -format_unit_label(l, u, f::NTuple{3,<:AbstractString}) = string(f[1], l, f[2], u, f[3]) -format_unit_label(l, u, f::Char) = string(l, ' ', f, ' ', u) -format_unit_label(l, u, f::NTuple{2,Char}) = string(l, ' ', f[1], u, f[2]) -format_unit_label(l, u, f::NTuple{3,Char}) = string(f[1], l, ' ', f[2], u, f[3]) -format_unit_label(l, u, f::Bool) = f ? format_unit_label(l, u, :round) : format_unit_label(l, u, nothing) -format_unit_label(l, u, f::Symbol) = format_unit_label(l, u, UNIT_FORMATS[f]) getaxisunit(::Nothing) = nothing getaxisunit(u) = u diff --git a/PlotsBase/src/Axes.jl b/PlotsBase/src/Axes.jl index 6d6320a3b..ee67d3353 100644 --- a/PlotsBase/src/Axes.jl +++ b/PlotsBase/src/Axes.jl @@ -327,6 +327,8 @@ function PlotsBase.attr!(axis::Axis, args...; kw...) foreach(x -> discrete_value!(axis, x), v) # add these discrete values to the axis elseif k ≡ :lims && isa(v, NTuple{2,Dates.TimeType}) plotattributes[k] = (Dates.value(v[1]), Dates.value(v[2])) + elseif k ≡ :guide && !isnothing(plotattributes[:unit]) + plotattributes[k] = format_unit_label(v, plotattributes[:unit], plotattributes[:unitformat]) else plotattributes[k] = v end @@ -340,6 +342,32 @@ function PlotsBase.attr!(axis::Axis, args...; kw...) axis end +format_unit_label(l, u, f::Nothing) = string(l, ' ', u) +format_unit_label(l, u, f::Function) = f(l, u) +format_unit_label(l, u, f::AbstractString) = string(l, f, u) +format_unit_label(l, u, f::NTuple{2,<:AbstractString}) = string(l, f[1], u, f[2]) +format_unit_label(l, u, f::NTuple{3,<:AbstractString}) = string(f[1], l, f[2], u, f[3]) +format_unit_label(l, u, f::Char) = string(l, ' ', f, ' ', u) +format_unit_label(l, u, f::NTuple{2,Char}) = string(l, ' ', f[1], u, f[2]) +format_unit_label(l, u, f::NTuple{3,Char}) = string(f[1], l, ' ', f[2], u, f[3]) +format_unit_label(l, u, f::Bool) = f ? format_unit_label(l, u, :round) : format_unit_label(l, u, nothing) +format_unit_label(l, u, f::Symbol) = format_unit_label(l, u, UNIT_FORMATS[f]) + +const UNIT_FORMATS = Dict( + :round => ('(', ')'), + :square => ('[', ']'), + :curly => ('{', '}'), + :angle => ('<', '>'), + :slash => '/', + :slashround => (" / (", ")"), + :slashsquare => (" / [", "]"), + :slashcurly => (" / {", "}"), + :slashangle => (" / <", ">"), + :verbose => " in units of ", + :none => nothing, +) + + # ----------------------------------------------------------------------------- Base.show(io::IO, axis::Axis) = Commons.dumpdict(io, axis.plotattributes, "Axis") From 5597ef39eb3987340ef48c87492cb38e0b293e25 Mon Sep 17 00:00:00 2001 From: Isaac Wheeler Date: Wed, 28 May 2025 13:29:45 +0200 Subject: [PATCH 3/9] Revert "Try out moving unitformat to axis guide set time" This reverts commit 1908c22401c268539c22de915476af2b02075feb. --- PlotsBase/ext/UnitfulExt.jl | 116 +++++++++++++++++++++--------------- PlotsBase/src/Axes.jl | 28 --------- 2 files changed, 69 insertions(+), 75 deletions(-) diff --git a/PlotsBase/ext/UnitfulExt.jl b/PlotsBase/ext/UnitfulExt.jl index 16ca28f2c..0169afe81 100644 --- a/PlotsBase/ext/UnitfulExt.jl +++ b/PlotsBase/ext/UnitfulExt.jl @@ -36,7 +36,7 @@ Main recipe if axisletter ≡ :z && get(plotattributes, :seriestype, :nothing) ∈ clims_types u = get(plotattributes, :zunit, _unit(eltype(x))) ustripattribute!(plotattributes, :clims, u) - # append_unit_if_needed!(plotattributes, :colorbar_title, u) + append_unit_if_needed!(plotattributes, :colorbar_title, u) end fixaxis!(plotattributes, x, axisletter) end @@ -44,6 +44,8 @@ end function fixaxis!(attr, x, axisletter) # Attribute keys axislabel = Symbol(axisletter, :guide) # xguide, yguide, zguide + axislims = Symbol(axisletter, :lims) # xlims, ylims, zlims + axisticks = Symbol(axisletter, :ticks) # xticks, yticks, zticks err = Symbol(axisletter, :error) # xerror, yerror, zerror axisunit = Symbol(axisletter, :unit) # xunit, yunit, zunit axis = Symbol(axisletter, :axis) # xaxis, yaxis, zaxis @@ -60,7 +62,7 @@ function fixaxis!(attr, x, axisletter) get!(attr, axislabel, label) # if label was not given as an argument, reuse end # fix the attributes: labels, lims, ticks, marker/line stuff, etc. - # append_unit_if_needed!(attr, axislabel, u) + append_unit_if_needed!(attr, axislabel, u) ustripattribute!(attr, err, u) if axisletter ≡ :y ustripattribute!(attr, :ribbon, u) @@ -78,7 +80,7 @@ end u = get(plotattributes, :zunit, _unit(eltype(z))) ustripattribute!(plotattributes, :clims, u) z = fixaxis!(plotattributes, z, :z) - # append_unit_if_needed!(plotattributes, :colorbar_title, u) + append_unit_if_needed!(plotattributes, :colorbar_title, u) x, y, z end @@ -174,14 +176,10 @@ end function fixmarkercolor!(attr) u = ustripattribute!(attr, :marker_z) ustripattribute!(attr, :clims, u) - # u == NoUnits || append_unit_if_needed!(attr, :colorbar_title, u) -end -function fixlinecolor!(attr) - u = ustripattribute!(attr, :line_z) - ustripattribute!(attr, :clims, u) - # u == NoUnits || append_unit_if_needed!(attr, :colorbar_title, u) + u == NoUnits || append_unit_if_needed!(attr, :colorbar_title, u) end fixmarkersize!(attr) = ustripattribute!(attr, :markersize) +fixlinecolor!(attr) = ustripattribute!(attr, :line_z) # strip unit from attribute[key] ustripattribute!(attr, key) = @@ -233,49 +231,73 @@ PlotsBase.protectedstring(s) = ProtectedString(s) Append unit to labels when appropriate =====================================# -# append_unit_if_needed!(attr, key, u) = -# append_unit_if_needed!(attr, key, get(attr, key, nothing), u) -# # dispatch on the type of `label` -# append_unit_if_needed!(attr, key, label::ProtectedString, u) = nothing -# append_unit_if_needed!(attr, key, label::UnitfulString, u) = nothing -# function append_unit_if_needed!(attr, key, label::Nothing, u) -# attr[key] = if PlotsBase.backend_name() ≡ :pgfplotsx -# UnitfulString(LaTeXString(Latexify.latexify(u)), u) -# else -# UnitfulString(string(u), u) -# end -# end -# function append_unit_if_needed!(attr, key, label::S, u) where {S<:AbstractString} -# isempty(label) && return attr[key] = UnitfulString(label, u) -# attr[key] = if PlotsBase.backend_name() ≡ :pgfplotsx -# UnitfulString( -# LaTeXString( -# format_unit_label( -# label, -# Latexify.latexify(u), -# get(attr, Symbol(get(attr, :letter, ""), :unitformat), :round), -# ), -# ), -# u, -# ) -# else -# UnitfulString( -# S( -# format_unit_label( -# label, -# u, -# get(attr, Symbol(get(attr, :letter, ""), :unitformat), :round), -# ), -# ), -# u, -# ) -# end -# end +append_unit_if_needed!(attr, key, u) = + append_unit_if_needed!(attr, key, get(attr, key, nothing), u) +# dispatch on the type of `label` +append_unit_if_needed!(attr, key, label::ProtectedString, u) = nothing +append_unit_if_needed!(attr, key, label::UnitfulString, u) = nothing +function append_unit_if_needed!(attr, key, label::Nothing, u) + attr[key] = if attr[:plot_object].backend == PlotsBase.backend_instance(:pgfplotsx) + UnitfulString(LaTeXString(Latexify.latexify(u)), u) + else + UnitfulString(string(u), u) + end +end +function append_unit_if_needed!(attr, key, label::S, u) where {S<:AbstractString} + isempty(label) && return attr[key] = UnitfulString(label, u) + attr[key] = if attr[:plot_object].backend == PlotsBase.backend_instance(:pgfplotsx) + UnitfulString( + LaTeXString( + format_unit_label( + label, + Latexify.latexify(u), + get(attr, Symbol(get(attr, :letter, ""), :unitformat), :round), + ), + ), + u, + ) + else + UnitfulString( + S( + format_unit_label( + label, + u, + get(attr, Symbol(get(attr, :letter, ""), :unitformat), :round), + ), + ), + u, + ) + end +end #============================================= Surround unit string with specified delimiters =============================================# +const UNIT_FORMATS = Dict( + :round => ('(', ')'), + :square => ('[', ']'), + :curly => ('{', '}'), + :angle => ('<', '>'), + :slash => '/', + :slashround => (" / (", ")"), + :slashsquare => (" / [", "]"), + :slashcurly => (" / {", "}"), + :slashangle => (" / <", ">"), + :verbose => " in units of ", + :none => nothing, +) + +format_unit_label(l, u, f::Nothing) = string(l, ' ', u) +format_unit_label(l, u, f::Function) = f(l, u) +format_unit_label(l, u, f::AbstractString) = string(l, f, u) +format_unit_label(l, u, f::NTuple{2,<:AbstractString}) = string(l, f[1], u, f[2]) +format_unit_label(l, u, f::NTuple{3,<:AbstractString}) = string(f[1], l, f[2], u, f[3]) +format_unit_label(l, u, f::Char) = string(l, ' ', f, ' ', u) +format_unit_label(l, u, f::NTuple{2,Char}) = string(l, ' ', f[1], u, f[2]) +format_unit_label(l, u, f::NTuple{3,Char}) = string(f[1], l, ' ', f[2], u, f[3]) +format_unit_label(l, u, f::Bool) = f ? format_unit_label(l, u, :round) : format_unit_label(l, u, nothing) +format_unit_label(l, u, f::Symbol) = format_unit_label(l, u, UNIT_FORMATS[f]) getaxisunit(::Nothing) = nothing getaxisunit(u) = u diff --git a/PlotsBase/src/Axes.jl b/PlotsBase/src/Axes.jl index ee67d3353..6d6320a3b 100644 --- a/PlotsBase/src/Axes.jl +++ b/PlotsBase/src/Axes.jl @@ -327,8 +327,6 @@ function PlotsBase.attr!(axis::Axis, args...; kw...) foreach(x -> discrete_value!(axis, x), v) # add these discrete values to the axis elseif k ≡ :lims && isa(v, NTuple{2,Dates.TimeType}) plotattributes[k] = (Dates.value(v[1]), Dates.value(v[2])) - elseif k ≡ :guide && !isnothing(plotattributes[:unit]) - plotattributes[k] = format_unit_label(v, plotattributes[:unit], plotattributes[:unitformat]) else plotattributes[k] = v end @@ -342,32 +340,6 @@ function PlotsBase.attr!(axis::Axis, args...; kw...) axis end -format_unit_label(l, u, f::Nothing) = string(l, ' ', u) -format_unit_label(l, u, f::Function) = f(l, u) -format_unit_label(l, u, f::AbstractString) = string(l, f, u) -format_unit_label(l, u, f::NTuple{2,<:AbstractString}) = string(l, f[1], u, f[2]) -format_unit_label(l, u, f::NTuple{3,<:AbstractString}) = string(f[1], l, f[2], u, f[3]) -format_unit_label(l, u, f::Char) = string(l, ' ', f, ' ', u) -format_unit_label(l, u, f::NTuple{2,Char}) = string(l, ' ', f[1], u, f[2]) -format_unit_label(l, u, f::NTuple{3,Char}) = string(f[1], l, ' ', f[2], u, f[3]) -format_unit_label(l, u, f::Bool) = f ? format_unit_label(l, u, :round) : format_unit_label(l, u, nothing) -format_unit_label(l, u, f::Symbol) = format_unit_label(l, u, UNIT_FORMATS[f]) - -const UNIT_FORMATS = Dict( - :round => ('(', ')'), - :square => ('[', ']'), - :curly => ('{', '}'), - :angle => ('<', '>'), - :slash => '/', - :slashround => (" / (", ")"), - :slashsquare => (" / [", "]"), - :slashcurly => (" / {", "}"), - :slashangle => (" / <", ">"), - :verbose => " in units of ", - :none => nothing, -) - - # ----------------------------------------------------------------------------- Base.show(io::IO, axis::Axis) = Commons.dumpdict(io, axis.plotattributes, "Axis") From 772d3480eec1824e3e3fc6a65a7bffec3987b2ed Mon Sep 17 00:00:00 2001 From: Isaac Wheeler Date: Wed, 28 May 2025 13:58:23 +0200 Subject: [PATCH 4/9] Minor cleanup --- PlotsBase/ext/UnitfulExt.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/PlotsBase/ext/UnitfulExt.jl b/PlotsBase/ext/UnitfulExt.jl index 0169afe81..34139de58 100644 --- a/PlotsBase/ext/UnitfulExt.jl +++ b/PlotsBase/ext/UnitfulExt.jl @@ -44,8 +44,6 @@ end function fixaxis!(attr, x, axisletter) # Attribute keys axislabel = Symbol(axisletter, :guide) # xguide, yguide, zguide - axislims = Symbol(axisletter, :lims) # xlims, ylims, zlims - axisticks = Symbol(axisletter, :ticks) # xticks, yticks, zticks err = Symbol(axisletter, :error) # xerror, yerror, zerror axisunit = Symbol(axisletter, :unit) # xunit, yunit, zunit axis = Symbol(axisletter, :axis) # xaxis, yaxis, zaxis @@ -178,8 +176,12 @@ function fixmarkercolor!(attr) ustripattribute!(attr, :clims, u) u == NoUnits || append_unit_if_needed!(attr, :colorbar_title, u) end +function fixlinecolor!(attr) + u = ustripattribute!(attr, :line_z) + ustripattribute!(attr, :clims, u) + u == NoUnits || append_unit_if_needed!(attr, :colorbar_title, u) +end fixmarkersize!(attr) = ustripattribute!(attr, :markersize) -fixlinecolor!(attr) = ustripattribute!(attr, :line_z) # strip unit from attribute[key] ustripattribute!(attr, key) = From 34497572fb52133f2d2ce12f8a00070337d165d2 Mon Sep 17 00:00:00 2001 From: Isaac Wheeler Date: Wed, 28 May 2025 13:59:40 +0200 Subject: [PATCH 5/9] Invasive solution --- PlotsBase/ext/GRExt.jl | 27 ++++++++++++-------- PlotsBase/ext/GastonExt.jl | 2 +- PlotsBase/ext/PGFPlotsXExt.jl | 2 +- PlotsBase/ext/PythonPlotExt.jl | 2 +- PlotsBase/ext/UnicodePlotsExt.jl | 4 +-- PlotsBase/ext/UnitfulExt.jl | 26 ------------------- PlotsBase/src/Axes.jl | 42 ++++++++++++++++++++++++++++++- PlotsBase/src/backends.jl | 2 +- PlotsBase/src/plotly.jl | 2 +- PlotsBase/test/test_axes.jl | 2 +- PlotsBase/test/test_shorthands.jl | 6 ++--- PlotsBase/test/test_unitful.jl | 6 ++--- StatsPlots/test/runtests.jl | 10 ++++---- 13 files changed, 76 insertions(+), 57 deletions(-) diff --git a/PlotsBase/ext/GRExt.jl b/PlotsBase/ext/GRExt.jl index 2cf959aec..79294f6a2 100644 --- a/PlotsBase/ext/GRExt.jl +++ b/PlotsBase/ext/GRExt.jl @@ -951,7 +951,7 @@ function gr_axis_height(sp, axis) ticks in (nothing, false, :none) ? 0 : last(gr_get_ticks_size(ticks, axis[:rotation])) ) - if (guide = axis[:guide]) != "" + if (guide = PlotsBase.get_guide(axis)) != "" gr_set_font(guidefont(axis), sp) h += last(gr_text_size(guide)) end @@ -967,7 +967,7 @@ function gr_axis_width(sp, axis) ticks in (nothing, false, :none) ? 0 : first(gr_get_ticks_size(ticks, axis[:rotation])) ) - if (guide = axis[:guide]) != "" + if (guide = PlotsBase.get_guide(axis)) != "" gr_set_font(guidefont(axis), sp) w += last(gr_text_size(guide)) end @@ -1038,7 +1038,7 @@ function PlotsBase._update_min_padding!(sp::Subplot{GRBackend}) # Add margin for x or y label m = 0mm for ax ∈ (xaxis, yaxis) - (guide = ax[:guide] == "") && continue + (guide = PlotsBase.get_guide(ax) == "") && continue gr_set_font(guidefont(ax), sp) l = last(gr_text_size(guide)) m = max(m, 1mm + height * l * px) @@ -1048,7 +1048,7 @@ function PlotsBase._update_min_padding!(sp::Subplot{GRBackend}) padding[mirrored(xaxis, :top) ? :top : :bottom][] += m end # Add margin for z label - if (guide = zaxis[:guide]) != "" + if (guide = PlotsBase.get_guide(zaxis)) != "" gr_set_font(guidefont(zaxis), sp) l = last(gr_text_size(guide)) padding[mirrored(zaxis, :right) ? :right : :left][] += 1mm + height * l * px # NOTE: why `height` here ? @@ -1064,7 +1064,7 @@ function PlotsBase._update_min_padding!(sp::Subplot{GRBackend}) l = 0.01 + (isy ? first(ts) : last(ts)) padding[ax[:mirror] ? a : b][] += 1mm + sp_size[isy ? 1 : 2] * l * px end - if (guide = ax[:guide]) != "" + if (guide = PlotsBase.get_guide(ax)) != "" gr_set_font(guidefont(ax), sp) l = last(gr_text_size(guide)) padding[mirrored(ax, a) ? a : b][] += 1mm + height * l * px # NOTE: using `height` is arbitrary @@ -1834,8 +1834,10 @@ function gr_label_ticks_3d(sp, letter, ticks) end end -gr_label_axis(sp, letter, vp) = - if (ax = sp[get_attr_symbol(letter, :axis)])[:guide] != "" +function gr_label_axis(sp, letter, vp) + ax = sp[get_attr_symbol(letter, :axis)] + if PlotsBase.get_guide(ax) != "" + @info "gr" ax PlotsBase.get_guide(ax) mirror = ax[:mirror] GR.savestate() guide_position = ax[:guide_position] @@ -1860,12 +1862,14 @@ gr_label_axis(sp, letter, vp) = end end gr_set_font(guidefont(ax), sp; rotation, halign, valign) - gr_text(xpos, ypos, ax[:guide]) + gr_text(xpos, ypos, PlotsBase.get_guide(ax)) GR.restorestate() end +end -gr_label_axis_3d(sp, letter) = - if (ax = sp[get_attr_symbol(letter, :axis)])[:guide] != "" +function gr_label_axis_3d(sp, letter) + ax = sp[get_attr_symbol(letter, :axis)] + if PlotsBase.get_guide(ax) != "" letters = axes_letters(sp, letter) (amin, amax), (namin, namax), (famin, famax) = map(l -> axis_limits(sp, l), letters) n0, n1 = letter ≡ :y ? (namax, namin) : (namin, namax) @@ -1893,9 +1897,10 @@ gr_label_axis_3d(sp, letter) = end letter ≡ :z && GR.setcharup(-1, 0) sgn = ax[:mirror] ? -1 : 1 - gr_text(x + sgn * x_offset, y + sgn * y_offset, ax[:guide]) + gr_text(x + sgn * x_offset, y + sgn * y_offset, PlotsBase.get_guide(ax)) GR.restorestate() end +end gr_add_title(sp, vp_plt, vp_sp) = if (title = sp[:title]) != "" diff --git a/PlotsBase/ext/GastonExt.jl b/PlotsBase/ext/GastonExt.jl index f3ccc5690..52e07275d 100644 --- a/PlotsBase/ext/GastonExt.jl +++ b/PlotsBase/ext/GastonExt.jl @@ -542,7 +542,7 @@ function gaston_parse_axes_attrs( end push!( axesconf, - "set $(letter)$(I)label '$(axis[:guide])' $(gaston_font(guide_font))", + "set $(letter)$(I)label '$(PlotsBase.get_guide(axis))' $(gaston_font(guide_font))", ) logscale, base = if (scale = axis[:scale]) ≡ :identity diff --git a/PlotsBase/ext/PGFPlotsXExt.jl b/PlotsBase/ext/PGFPlotsXExt.jl index eeda1ce8c..d1e84dcfc 100644 --- a/PlotsBase/ext/PGFPlotsXExt.jl +++ b/PlotsBase/ext/PGFPlotsXExt.jl @@ -1357,7 +1357,7 @@ function pgfx_axis!(opt::Options, sp::Subplot, letter) push!( opt, "scaled $(letter) ticks" => "false", - "$(letter)label" => axis[:guide], + "$(letter)label" => PlotsBase.get_guide(axis), "$(letter) tick style" => Options("color" => color(tick_color), "opacity" => alpha(tick_color)), "$(letter) tick label style" => Options( diff --git a/PlotsBase/ext/PythonPlotExt.jl b/PlotsBase/ext/PythonPlotExt.jl index 57bcc024d..1bf266320 100644 --- a/PlotsBase/ext/PythonPlotExt.jl +++ b/PlotsBase/ext/PythonPlotExt.jl @@ -1309,7 +1309,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) pyaxis.set_major_locator(mpl.ticker.NullLocator()) end - getproperty(ax, set_axis(letter, :label))(axis[:guide]) + getproperty(ax, set_axis(letter, :label))(PlotsBase.get_guide(axis)) pyaxis.label.set_fontsize(_py_thickness_scale(plt, axis[:guidefontsize])) pyaxis.label.set_family(axis[:guidefontfamily]) pyaxis.label.set_math_fontfamily( diff --git a/PlotsBase/ext/UnicodePlotsExt.jl b/PlotsBase/ext/UnicodePlotsExt.jl index c6c8bc728..a56a4fa84 100644 --- a/PlotsBase/ext/UnicodePlotsExt.jl +++ b/PlotsBase/ext/UnicodePlotsExt.jl @@ -195,8 +195,8 @@ function PlotsBase._before_layout_calcs(plt::Plot{UnicodePlotsBackend}) kw = ( compact = true, title = texmath2unicode(sp[:title]), - xlabel = texmath2unicode(xaxis[:guide]), - ylabel = texmath2unicode(yaxis[:guide]), + xlabel = texmath2unicode(PlotsBase.get_guide(xaxis)), + ylabel = texmath2unicode(PlotsBase.get_guide(yaxis)), labels = !plot_3d, # guide labels and limits do not make sense in 3d xscale = xaxis[:scale], yscale = yaxis[:scale], diff --git a/PlotsBase/ext/UnitfulExt.jl b/PlotsBase/ext/UnitfulExt.jl index 34139de58..3e918eeb1 100644 --- a/PlotsBase/ext/UnitfulExt.jl +++ b/PlotsBase/ext/UnitfulExt.jl @@ -60,7 +60,6 @@ function fixaxis!(attr, x, axisletter) get!(attr, axislabel, label) # if label was not given as an argument, reuse end # fix the attributes: labels, lims, ticks, marker/line stuff, etc. - append_unit_if_needed!(attr, axislabel, u) ustripattribute!(attr, err, u) if axisletter ≡ :y ustripattribute!(attr, :ribbon, u) @@ -276,31 +275,6 @@ end Surround unit string with specified delimiters =============================================# -const UNIT_FORMATS = Dict( - :round => ('(', ')'), - :square => ('[', ']'), - :curly => ('{', '}'), - :angle => ('<', '>'), - :slash => '/', - :slashround => (" / (", ")"), - :slashsquare => (" / [", "]"), - :slashcurly => (" / {", "}"), - :slashangle => (" / <", ">"), - :verbose => " in units of ", - :none => nothing, -) - -format_unit_label(l, u, f::Nothing) = string(l, ' ', u) -format_unit_label(l, u, f::Function) = f(l, u) -format_unit_label(l, u, f::AbstractString) = string(l, f, u) -format_unit_label(l, u, f::NTuple{2,<:AbstractString}) = string(l, f[1], u, f[2]) -format_unit_label(l, u, f::NTuple{3,<:AbstractString}) = string(f[1], l, f[2], u, f[3]) -format_unit_label(l, u, f::Char) = string(l, ' ', f, ' ', u) -format_unit_label(l, u, f::NTuple{2,Char}) = string(l, ' ', f[1], u, f[2]) -format_unit_label(l, u, f::NTuple{3,Char}) = string(f[1], l, ' ', f[2], u, f[3]) -format_unit_label(l, u, f::Bool) = f ? format_unit_label(l, u, :round) : format_unit_label(l, u, nothing) -format_unit_label(l, u, f::Symbol) = format_unit_label(l, u, UNIT_FORMATS[f]) - getaxisunit(::Nothing) = nothing getaxisunit(u) = u getaxisunit(a::Axis) = getaxisunit(a[:unit]) diff --git a/PlotsBase/src/Axes.jl b/PlotsBase/src/Axes.jl index 6d6320a3b..1a9c15124 100644 --- a/PlotsBase/src/Axes.jl +++ b/PlotsBase/src/Axes.jl @@ -1,7 +1,7 @@ module Axes export Axis, Extrema, tickfont, guidefont, widen_factor, scale_inverse_scale_func -export sort_3d_axes, axes_letters, process_axis_arg!, has_ticks, get_axis +export sort_3d_axes, axes_letters, process_axis_arg!, has_ticks, get_axis, get_guide import ..PlotsBase import ..PlotsBase: Subplot, DefaultsDict, attr! @@ -311,6 +311,7 @@ end has_ticks(axis::Axis) = _has_ticks(get(axis, :ticks, nothing)) + # update an Axis object with magic args and keywords function PlotsBase.attr!(axis::Axis, args...; kw...) # first process args @@ -340,6 +341,45 @@ function PlotsBase.attr!(axis::Axis, args...; kw...) axis end +function get_guide(axis::Axis) + if !isnothing(axis[:unit]) + @info "unit" axis[:guide] axis[:unit] + return format_unit_label( + axis[:guide], + axis[:unit], + axis[:unitformat]) + else + @info "no unit" axis[:guide] axis[:unit] + return axis[:guide] + end +end + +const UNIT_FORMATS = Dict( + :round => ('(', ')'), + :square => ('[', ']'), + :curly => ('{', '}'), + :angle => ('<', '>'), + :slash => '/', + :slashround => (" / (", ")"), + :slashsquare => (" / [", "]"), + :slashcurly => (" / {", "}"), + :slashangle => (" / <", ">"), + :verbose => " in units of ", + :none => nothing, +) + +format_unit_label(l, u, f::Nothing) = string(l, ' ', u) +format_unit_label(l, u, f::Function) = f(l, u) +format_unit_label(l, u, f::AbstractString) = string(l, f, u) +format_unit_label(l, u, f::NTuple{2,<:AbstractString}) = string(l, f[1], u, f[2]) +format_unit_label(l, u, f::NTuple{3,<:AbstractString}) = string(f[1], l, f[2], u, f[3]) +format_unit_label(l, u, f::Char) = string(l, ' ', f, ' ', u) +format_unit_label(l, u, f::NTuple{2,Char}) = string(l, ' ', f[1], u, f[2]) +format_unit_label(l, u, f::NTuple{3,Char}) = string(f[1], l, ' ', f[2], u, f[3]) +format_unit_label(l, u, f::Bool) = f ? format_unit_label(l, u, :round) : format_unit_label(l, u, nothing) +format_unit_label(l, u, f::Symbol) = format_unit_label(l, u, UNIT_FORMATS[f]) + + # ----------------------------------------------------------------------------- Base.show(io::IO, axis::Axis) = Commons.dumpdict(io, axis.plotattributes, "Axis") diff --git a/PlotsBase/src/backends.jl b/PlotsBase/src/backends.jl index c94d56aa8..f372c53a4 100644 --- a/PlotsBase/src/backends.jl +++ b/PlotsBase/src/backends.jl @@ -57,7 +57,7 @@ _series_updated(::Plot, ::Series) = nothing _before_layout_calcs(plt::Plot) = nothing title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefontsize] * pt -guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefontsize] * pt +guide_padding(axis::Axis) = PlotsBase.get_guide(axis) == "" ? 0mm : axis[:guidefontsize] * pt closeall(::AbstractBackend) = nothing diff --git a/PlotsBase/src/plotly.jl b/PlotsBase/src/plotly.jl index 164887cc9..15479d75f 100644 --- a/PlotsBase/src/plotly.jl +++ b/PlotsBase/src/plotly.jl @@ -298,7 +298,7 @@ function plotly_axis(axis, sp, anchor = nothing, domain = nothing) framestyle = sp[:framestyle] ax = KW( :visible => framestyle ≢ :none, - :title => axis[:guide], + :title => PlotsBase.get_guide(axis), :showgrid => axis[:grid], :gridcolor => rgba_string(plot_color(axis[:foreground_color_grid], axis[:gridalpha])), diff --git a/PlotsBase/test/test_axes.jl b/PlotsBase/test/test_axes.jl index 5a052d736..a9ac05d64 100644 --- a/PlotsBase/test/test_axes.jl +++ b/PlotsBase/test/test_axes.jl @@ -158,7 +158,7 @@ end @test haskey(PlotsBase.Commons._keyAliases, :x_guide_position) @test !haskey(PlotsBase.Commons._keyAliases, :xguide_position) pl = plot(1:2, xl = "x label") - @test pl[1][:xaxis][:guide] ≡ "x label" + @test PlotsBase.get_guide(pl[1][:xaxis]) ≡ "x label" pl = plot(1:2, xrange = (0, 3)) @test xlims(pl) ≡ (0, 3) pl = plot(1:2, xtick = [1.25, 1.5, 1.75]) diff --git a/PlotsBase/test/test_shorthands.jl b/PlotsBase/test/test_shorthands.jl index 69a9cdf28..4598df034 100644 --- a/PlotsBase/test/test_shorthands.jl +++ b/PlotsBase/test/test_shorthands.jl @@ -33,11 +33,11 @@ end sp = pl[1] @test sp[:title] == "Foo" xlabel!(pl, "xlabel") - @test sp[:xaxis][:guide] == "xlabel" + @test PlotsBase.get_guide(sp[:xaxis]) == "xlabel" ylabel!(pl, "ylabel") - @test sp[:yaxis][:guide] == "ylabel" + @test PlotsBase.get_guide(sp[:yaxis]) == "ylabel" zlabel!(pl, "zlabel") - @test sp[:zaxis][:guide] == "zlabel" + @test PlotsBase.get_guide(sp[:zaxis]) == "zlabel" end @testset "Misc" begin diff --git a/PlotsBase/test/test_unitful.jl b/PlotsBase/test/test_unitful.jl index e9b141b14..35c12cf7d 100644 --- a/PlotsBase/test/test_unitful.jl +++ b/PlotsBase/test/test_unitful.jl @@ -2,9 +2,9 @@ using PlotsBase, Test using Unitful using Unitful: m, cm, s, DimensionError # Some helper functions to access the subplot labels and the series inside each test plot -xguide(pl, idx = length(pl.subplots)) = pl.subplots[idx].attr[:xaxis].plotattributes[:guide] -yguide(pl, idx = length(pl.subplots)) = pl.subplots[idx].attr[:yaxis].plotattributes[:guide] -zguide(pl, idx = length(pl.subplots)) = pl.subplots[idx].attr[:zaxis].plotattributes[:guide] +xguide(pl, idx = length(pl.subplots)) = PlotsBase.get_guide(pl.subplots[idx].attr[:xaxis].plotattributes) +yguide(pl, idx = length(pl.subplots)) = PlotsBase.get_guide(pl.subplots[idx].attr[:yaxis].plotattributes) +zguide(pl, idx = length(pl.subplots)) = PlotsBase.get_guide(pl.subplots[idx].attr[:zaxis].plotattributes) xseries(pl, idx = length(pl.series_list)) = pl.series_list[idx].plotattributes[:x] yseries(pl, idx = length(pl.series_list)) = pl.series_list[idx].plotattributes[:y] zseries(pl, idx = length(pl.series_list)) = pl.series_list[idx].plotattributes[:z] diff --git a/StatsPlots/test/runtests.jl b/StatsPlots/test/runtests.jl index 5614079be..5a2de9eae 100644 --- a/StatsPlots/test/runtests.jl +++ b/StatsPlots/test/runtests.jl @@ -145,16 +145,16 @@ end mds_plt = plot(M) @test mds_plt[1][1][:x] == Y[:, 1] @test mds_plt[1][1][:y] == Y[:, 2] - @test mds_plt[1][:xaxis][:guide] == "MDS1" - @test mds_plt[1][:yaxis][:guide] == "MDS2" + @test PlotsBase.get_guide(mds_plt[1][:xaxis]) == "MDS1" + @test PlotsBase.get_guide(mds_plt[1][:yaxis]) == "MDS2" mds_plt2 = plot(M; mds_axes = (3, 1, 2)) @test mds_plt2[1][1][:x] == Y[:, 3] @test mds_plt2[1][1][:y] == Y[:, 1] @test mds_plt2[1][1][:z] == Y[:, 2] - @test mds_plt2[1][:xaxis][:guide] == "MDS3" - @test mds_plt2[1][:yaxis][:guide] == "MDS1" - @test mds_plt2[1][:zaxis][:guide] == "MDS2" + @test PlotsBase.get_guide(mds_plt2[1][:xaxis]) == "MDS3" + @test PlotsBase.get_guide(mds_plt2[1][:yaxis]) == "MDS1" + @test PlotsBase.get_guide(mds_plt2[1][:zaxis]) == "MDS2" end end From d39ac9cb433f366df5940d418096b0d434f1e5d9 Mon Sep 17 00:00:00 2001 From: Isaac Wheeler Date: Mon, 2 Jun 2025 10:21:15 +0200 Subject: [PATCH 6/9] Remove debug statement, oops --- PlotsBase/src/Axes.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/PlotsBase/src/Axes.jl b/PlotsBase/src/Axes.jl index 1a9c15124..3848e8978 100644 --- a/PlotsBase/src/Axes.jl +++ b/PlotsBase/src/Axes.jl @@ -343,13 +343,11 @@ end function get_guide(axis::Axis) if !isnothing(axis[:unit]) - @info "unit" axis[:guide] axis[:unit] return format_unit_label( axis[:guide], axis[:unit], axis[:unitformat]) else - @info "no unit" axis[:guide] axis[:unit] return axis[:guide] end end From a14eee7a9b310c54236bf350e15bda4757c4a8de Mon Sep 17 00:00:00 2001 From: Isaac Wheeler Date: Mon, 2 Jun 2025 10:52:24 +0200 Subject: [PATCH 7/9] remove another debug --- PlotsBase/ext/GRExt.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/PlotsBase/ext/GRExt.jl b/PlotsBase/ext/GRExt.jl index 79294f6a2..b0def3d36 100644 --- a/PlotsBase/ext/GRExt.jl +++ b/PlotsBase/ext/GRExt.jl @@ -1837,7 +1837,6 @@ end function gr_label_axis(sp, letter, vp) ax = sp[get_attr_symbol(letter, :axis)] if PlotsBase.get_guide(ax) != "" - @info "gr" ax PlotsBase.get_guide(ax) mirror = ax[:mirror] GR.savestate() guide_position = ax[:guide_position] From 967a9a67bd6f07fb3e8ef178163e1dca4eb5c187 Mon Sep 17 00:00:00 2001 From: Isaac Wheeler Date: Mon, 2 Jun 2025 17:27:41 +0200 Subject: [PATCH 8/9] Get UnitfulExt tests passing --- PlotsBase/ext/UnitfulExt.jl | 24 +++++++----------------- PlotsBase/src/Axes.jl | 25 +++++++++++++++++++++---- PlotsBase/src/layouts.jl | 1 + PlotsBase/src/utils.jl | 23 +++++++++++++++++++---- PlotsBase/test/test_unitful.jl | 6 +++--- 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/PlotsBase/ext/UnitfulExt.jl b/PlotsBase/ext/UnitfulExt.jl index 3e918eeb1..89a41ad70 100644 --- a/PlotsBase/ext/UnitfulExt.jl +++ b/PlotsBase/ext/UnitfulExt.jl @@ -17,7 +17,9 @@ import Unitful: Level, Gain, uconvert -import PlotsBase: PlotsBase, @recipe, PlotText, Subplot, AVec, AMat, Axis +import PlotsBase: PlotsBase, @recipe, PlotText, Subplot, AVec, AMat, Axis, + AbstractProtectedString, ProtectedString +import PlotsBase.Axes: format_unit_label import RecipesBase import LaTeXStrings: LaTeXString import Latexify @@ -57,7 +59,7 @@ function fixaxis!(attr, x, axisletter) u = spu end attr[axisunit] = u # update the unit in the attributes - get!(attr, axislabel, label) # if label was not given as an argument, reuse + # get!(attr, axislabel, label) # if label was not given as an argument, reuse end # fix the attributes: labels, lims, ticks, marker/line stuff, etc. ustripattribute!(attr, err, u) @@ -208,28 +210,16 @@ end Label string containing unit information =======================================# -abstract type AbstractProtectedString <: AbstractString end -struct ProtectedString{S} <: AbstractProtectedString - content::S -end struct UnitfulString{S,U} <: AbstractProtectedString content::S unit::U end -# Minimum required AbstractString interface to work with PlotsBase -const S = AbstractProtectedString -Base.iterate(n::S) = iterate(n.content) -Base.iterate(n::S, i::Integer) = iterate(n.content, i) -Base.codeunit(n::S) = codeunit(n.content) -Base.ncodeunits(n::S) = ncodeunits(n.content) -Base.isvalid(n::S, i::Integer) = isvalid(n.content, i) -Base.pointer(n::S) = pointer(n.content) -Base.pointer(n::S, i::Integer) = pointer(n.content, i) - -PlotsBase.protectedstring(s) = ProtectedString(s) + #===================================== Append unit to labels when appropriate +This is needed for colorbars, etc., since axes have +distinct unit handling =====================================# append_unit_if_needed!(attr, key, u) = diff --git a/PlotsBase/src/Axes.jl b/PlotsBase/src/Axes.jl index 3848e8978..f63f852af 100644 --- a/PlotsBase/src/Axes.jl +++ b/PlotsBase/src/Axes.jl @@ -328,6 +328,9 @@ function PlotsBase.attr!(axis::Axis, args...; kw...) foreach(x -> discrete_value!(axis, x), v) # add these discrete values to the axis elseif k ≡ :lims && isa(v, NTuple{2,Dates.TimeType}) plotattributes[k] = (Dates.value(v[1]), Dates.value(v[2])) + elseif k ≡ :guide && v isa AbstractString && isempty(v) + plotattributes[:unitformat] = :none + plotattributes[k] = v else plotattributes[k] = v end @@ -342,16 +345,29 @@ function PlotsBase.attr!(axis::Axis, args...; kw...) end function get_guide(axis::Axis) - if !isnothing(axis[:unit]) + if isnothing(axis[:unit]) || axis[:guide] isa PlotsBase.ProtectedString || + axis[:unitformat] == :none + return axis[:guide] + else + ustr = if PlotsBase.backend_name() ≡ :pgfplotsx + pgext = Base.get_extension(PlotsBase, :PGFPlotsXExt) + isnothing(pgext) && error("PGFPlotsX extension not loaded. Problems") + pgext.Latexify.latexify(axis[:unit]) + else + string(axis[:unit]) + end + if isempty(axis[:guide]) + return ustr + end return format_unit_label( axis[:guide], - axis[:unit], + ustr, axis[:unitformat]) - else - return axis[:guide] end end + +# Keyword options for unit formats const UNIT_FORMATS = Dict( :round => ('(', ')'), :square => ('[', ']'), @@ -366,6 +382,7 @@ const UNIT_FORMATS = Dict( :none => nothing, ) +# All options for unit formats format_unit_label(l, u, f::Nothing) = string(l, ' ', u) format_unit_label(l, u, f::Function) = f(l, u) format_unit_label(l, u, f::AbstractString) = string(l, f, u) diff --git a/PlotsBase/src/layouts.jl b/PlotsBase/src/layouts.jl index 26e95d8df..0ecef30ac 100644 --- a/PlotsBase/src/layouts.jl +++ b/PlotsBase/src/layouts.jl @@ -425,6 +425,7 @@ function twin(sp, letter) tax[:grid] = false tax[:showaxis] = false tax[:ticks] = :none + tax[:unitformat] = :none tax[:unit] = orig_sp[get_attr_symbol(letter, :axis)][:unit] oax[:grid] = false oax[:mirror] = true diff --git a/PlotsBase/src/utils.jl b/PlotsBase/src/utils.jl index 619e24c39..5bea2e9ca 100644 --- a/PlotsBase/src/utils.jl +++ b/PlotsBase/src/utils.jl @@ -931,14 +931,12 @@ end _generate_doclist(attributes) = replace(join(sort(collect(attributes)), "\n- "), "_" => "\\_") -# for UnitfulExt - cannot reside in `UnitfulExt` (macro) -function protectedstring end # COV_EXCL_LINE - """ P_str(s) (Unitful extension only). -Creates a string that will be Protected from recipe passes. +Creates a string that will not have units appended. +This is equivalent to passing `[x]unitformat = (l,u) -> l` to the plot command. Example: ```julia @@ -954,6 +952,23 @@ end # for `PGFPlotsx` together with `UnitfulExt` function pgfx_sanitize_string end # COV_EXCL_LINE +# Old API: a ProtectedString implicitly has unitformat = (l,u) -> l +# Can be removed for 2.0? +abstract type AbstractProtectedString <: AbstractString end +struct ProtectedString{S} <: AbstractProtectedString + content::S +end +const APS = AbstractProtectedString +# Minimum required AbstractString interface to work with PlotsBase +Base.iterate(n::APS) = iterate(n.content) +Base.iterate(n::APS, i::Integer) = iterate(n.content, i) +Base.codeunit(n::APS) = codeunit(n.content) +Base.ncodeunits(n::APS) = ncodeunits(n.content) +Base.isvalid(n::APS, i::Integer) = isvalid(n.content, i) +Base.pointer(n::APS) = pointer(n.content) +Base.pointer(n::APS, i::Integer) = pointer(n.content, i) +protectedstring(s) = ProtectedString(s) + function extrema_plus_buffer(v, buffmult = 0.2) vmin, vmax = ignorenan_extrema(v) vdiff = vmax - vmin diff --git a/PlotsBase/test/test_unitful.jl b/PlotsBase/test/test_unitful.jl index 35c12cf7d..752da7fdf 100644 --- a/PlotsBase/test/test_unitful.jl +++ b/PlotsBase/test/test_unitful.jl @@ -2,9 +2,9 @@ using PlotsBase, Test using Unitful using Unitful: m, cm, s, DimensionError # Some helper functions to access the subplot labels and the series inside each test plot -xguide(pl, idx = length(pl.subplots)) = PlotsBase.get_guide(pl.subplots[idx].attr[:xaxis].plotattributes) -yguide(pl, idx = length(pl.subplots)) = PlotsBase.get_guide(pl.subplots[idx].attr[:yaxis].plotattributes) -zguide(pl, idx = length(pl.subplots)) = PlotsBase.get_guide(pl.subplots[idx].attr[:zaxis].plotattributes) +xguide(pl, idx = length(pl.subplots)) = PlotsBase.get_guide(pl.subplots[idx].attr[:xaxis]) +yguide(pl, idx = length(pl.subplots)) = PlotsBase.get_guide(pl.subplots[idx].attr[:yaxis]) +zguide(pl, idx = length(pl.subplots)) = PlotsBase.get_guide(pl.subplots[idx].attr[:zaxis]) xseries(pl, idx = length(pl.series_list)) = pl.series_list[idx].plotattributes[:x] yseries(pl, idx = length(pl.series_list)) = pl.series_list[idx].plotattributes[:y] zseries(pl, idx = length(pl.series_list)) = pl.series_list[idx].plotattributes[:z] From cd7b4c715169dd58b44c0cec8d5788633cdc4ffa Mon Sep 17 00:00:00 2001 From: t-bltg Date: Mon, 9 Jun 2025 17:12:08 +0200 Subject: [PATCH 9/9] format --- PlotsBase/ext/UnitfulExt.jl | 17 +++++++++++++---- PlotsBase/src/Axes.jl | 13 ++++--------- PlotsBase/src/backends.jl | 3 ++- PlotsBase/src/layouts.jl | 5 ++--- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/PlotsBase/ext/UnitfulExt.jl b/PlotsBase/ext/UnitfulExt.jl index 7c50c7a95..3824db703 100644 --- a/PlotsBase/ext/UnitfulExt.jl +++ b/PlotsBase/ext/UnitfulExt.jl @@ -17,8 +17,16 @@ import Unitful: Level, Gain, uconvert -import PlotsBase: PlotsBase, @recipe, PlotText, Subplot, AVec, AMat, Axis, - AbstractProtectedString, ProtectedString +import PlotsBase: + PlotsBase, + @recipe, + PlotText, + Subplot, + AVec, + AMat, + Axis, + AbstractProtectedString, + ProtectedString import PlotsBase.Axes: format_unit_label import RecipesBase import LaTeXStrings: LaTeXString @@ -215,7 +223,6 @@ struct UnitfulString{S,U} <: AbstractProtectedString unit::U end - #===================================== Append unit to labels when appropriate This is needed for colorbars, etc., since axes have @@ -299,7 +306,9 @@ function PlotsBase.locate_annotation( rel::NTuple{N,<:MissingOrQuantity}, label, ) where {N} - units = getaxisunit(sp.attr[:xaxis]), getaxisunit(sp.attr[:yaxis]), getaxisunit(sp.attr[:zaxis]) + units = getaxisunit(sp.attr[:xaxis]), + getaxisunit(sp.attr[:yaxis]), + getaxisunit(sp.attr[:zaxis]) PlotsBase.locate_annotation(sp, _ustrip.(zip(units, rel)), label) end diff --git a/PlotsBase/src/Axes.jl b/PlotsBase/src/Axes.jl index f63f852af..db60d4bdb 100644 --- a/PlotsBase/src/Axes.jl +++ b/PlotsBase/src/Axes.jl @@ -311,7 +311,6 @@ end has_ticks(axis::Axis) = _has_ticks(get(axis, :ticks, nothing)) - # update an Axis object with magic args and keywords function PlotsBase.attr!(axis::Axis, args...; kw...) # first process args @@ -345,8 +344,9 @@ function PlotsBase.attr!(axis::Axis, args...; kw...) end function get_guide(axis::Axis) - if isnothing(axis[:unit]) || axis[:guide] isa PlotsBase.ProtectedString || - axis[:unitformat] == :none + if isnothing(axis[:unit]) || + axis[:guide] isa PlotsBase.ProtectedString || + axis[:unitformat] == :none return axis[:guide] else ustr = if PlotsBase.backend_name() ≡ :pgfplotsx @@ -359,14 +359,10 @@ function get_guide(axis::Axis) if isempty(axis[:guide]) return ustr end - return format_unit_label( - axis[:guide], - ustr, - axis[:unitformat]) + return format_unit_label(axis[:guide], ustr, axis[:unitformat]) end end - # Keyword options for unit formats const UNIT_FORMATS = Dict( :round => ('(', ')'), @@ -394,7 +390,6 @@ format_unit_label(l, u, f::NTuple{3,Char}) = string(f[1], l, ' ', f[ format_unit_label(l, u, f::Bool) = f ? format_unit_label(l, u, :round) : format_unit_label(l, u, nothing) format_unit_label(l, u, f::Symbol) = format_unit_label(l, u, UNIT_FORMATS[f]) - # ----------------------------------------------------------------------------- Base.show(io::IO, axis::Axis) = Commons.dumpdict(io, axis.plotattributes, "Axis") diff --git a/PlotsBase/src/backends.jl b/PlotsBase/src/backends.jl index f372c53a4..588a42890 100644 --- a/PlotsBase/src/backends.jl +++ b/PlotsBase/src/backends.jl @@ -57,7 +57,8 @@ _series_updated(::Plot, ::Series) = nothing _before_layout_calcs(plt::Plot) = nothing title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefontsize] * pt -guide_padding(axis::Axis) = PlotsBase.get_guide(axis) == "" ? 0mm : axis[:guidefontsize] * pt +guide_padding(axis::Axis) = + PlotsBase.get_guide(axis) == "" ? 0mm : axis[:guidefontsize] * pt closeall(::AbstractBackend) = nothing diff --git a/PlotsBase/src/layouts.jl b/PlotsBase/src/layouts.jl index 0ecef30ac..827146b5b 100644 --- a/PlotsBase/src/layouts.jl +++ b/PlotsBase/src/layouts.jl @@ -335,9 +335,8 @@ function link_axes!(axes::Axis...) a1 = axes[1] for i ∈ 2:length(axes) a2 = axes[i] - a1[:unit] ≡ a2[:unit] || error( - "Cannot link axes with different units: $(a1[:unit]) and $(a2[:unit])", - ) + a1[:unit] ≡ a2[:unit] || + error("Cannot link axes with different units: $(a1[:unit]) and $(a2[:unit])") expand_extrema!(a1, Axes.ignorenan_extrema(a2)) for k ∈ (:extrema, :discrete_values, :continuous_values, :discrete_map) a2[k] = a1[k]