Skip to content

Add unit axis attribute (for v2) #5095

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

Open
wants to merge 15 commits into
base: v2
Choose a base branch
from
Open
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
26 changes: 15 additions & 11 deletions PlotsBase/ext/GRExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 ?
Expand All @@ -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
Expand Down Expand Up @@ -1834,8 +1834,9 @@ 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) != ""
mirror = ax[:mirror]
GR.savestate()
guide_position = ax[:guide_position]
Expand All @@ -1860,12 +1861,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)
Expand Down Expand Up @@ -1893,9 +1896,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]) != ""
Expand Down
2 changes: 1 addition & 1 deletion PlotsBase/ext/GastonExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion PlotsBase/ext/PGFPlotsXExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion PlotsBase/ext/PythonPlotExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions PlotsBase/ext/UnicodePlotsExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
83 changes: 31 additions & 52 deletions PlotsBase/ext/UnitfulExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ 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
Expand All @@ -44,21 +54,22 @@ 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
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)
get!(attr, axislabel, label) # if label was not given as an argument, reuse
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.
append_unit_if_needed!(attr, axislabel, u)
ustripattribute!(attr, err, u)
if axisletter ≡ :y
ustripattribute!(attr, :ribbon, u)
Expand Down Expand Up @@ -174,8 +185,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) =
Expand Down Expand Up @@ -203,28 +218,15 @@ 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) =
Expand Down Expand Up @@ -270,34 +272,9 @@ 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(::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
Expand Down Expand Up @@ -329,7 +306,9 @@ 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

Expand Down
52 changes: 51 additions & 1 deletion PlotsBase/src/Axes.jl
Original file line number Diff line number Diff line change
@@ -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!
Expand Down Expand Up @@ -327,6 +327,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
Expand All @@ -340,6 +343,53 @@ function PlotsBase.attr!(axis::Axis, args...; kw...)
axis
end

function get_guide(axis::Axis)
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], ustr, axis[:unitformat])
end
end

# Keyword options for unit formats
const UNIT_FORMATS = Dict(
:round => ('(', ')'),
:square => ('[', ']'),
:curly => ('{', '}'),
:angle => ('<', '>'),
:slash => '/',
:slashround => (" / (", ")"),
:slashsquare => (" / [", "]"),
:slashcurly => (" / {", "}"),
:slashangle => (" / <", ">"),
:verbose => " in units of ",
: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)
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")
Expand Down
1 change: 1 addition & 0 deletions PlotsBase/src/Commons/attrs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ const _axis_defaults = KW(
:widen => :auto,
:draw_arrow => false,
:unitformat => :round,
:unit => nothing,
)

# add defaults for the letter versions
Expand Down
3 changes: 2 additions & 1 deletion PlotsBase/src/backends.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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) = axis[:guide] == "" ? 0mm : axis[:guidefontsize] * pt
guide_padding(axis::Axis) =
PlotsBase.get_guide(axis) == "" ? 0mm : axis[:guidefontsize] * pt

closeall(::AbstractBackend) = nothing

Expand Down
4 changes: 4 additions & 0 deletions PlotsBase/src/layouts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +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])")
expand_extrema!(a1, Axes.ignorenan_extrema(a2))
for k ∈ (:extrema, :discrete_values, :continuous_values, :discrete_map)
a2[k] = a1[k]
Expand Down Expand Up @@ -422,6 +424,8 @@ 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
twin_sp[:background_color_inside] = RGBA{Float64}(0, 0, 0, 0)
Expand Down
2 changes: 1 addition & 1 deletion PlotsBase/src/plotly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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])),
Expand Down
Loading
Loading