Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
36 changes: 19 additions & 17 deletions src/UnitfulRecipes.jl
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
module UnitfulRecipes

using RecipesBase
using Unitful: Quantity, unit, ustrip, Unitful, dimension, Units
using Unitful: Quantity, unit, ustrip, Unitful, dimension, Units, MixedUnits
export @P_str

include("UnitfulWrapper.jl")

const clims_types = (:contour, :contourf, :heatmap, :surface)

#==========
Main recipe
==========#

@recipe function f(::Type{T}, x::T) where T <: AbstractArray{<:Union{Missing,<:Quantity}}
@recipe function f(::Type{T}, x::T) where T <: AbstractArray{<:Union{Missing,<:Quantity, <:LogScaled}}
axisletter = plotattributes[:letter] # x, y, or z
if (axisletter == :z) &&
get(plotattributes, :seriestype, :nothing) ∈ clims_types
u = get(plotattributes, :zunit, unit(eltype(x)))
u = get(plotattributes, :zunit, fullunit(eltype(x)))
ustripattribute!(plotattributes, :clims, u)
append_unit_if_needed!(plotattributes, :colorbar_title, u)
end
Expand All @@ -30,7 +32,7 @@ function fixaxis!(attr, x, axisletter)
axisunit = Symbol(axisletter, :unit) # xunit, yunit, zunit
axis = Symbol(axisletter, :axis) # xaxis, yaxis, zaxis
# Get the unit
u = pop!(attr, axisunit, unit(eltype(x)))
u = pop!(attr, axisunit, fullunit(eltype(x)))
# 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
Expand Down Expand Up @@ -61,42 +63,42 @@ end
const AVec = AbstractVector
const AMat{T} = AbstractArray{T,2} where T
@recipe function f(x::AVec, y::AVec, z::AMat{T}) where T <: Quantity
u = get(plotattributes, :zunit, unit(eltype(z)))
u = get(plotattributes, :zunit, fullunit(eltype(z)))
ustripattribute!(plotattributes, :clims, u)
z = fixaxis!(plotattributes, z, :z)
append_unit_if_needed!(plotattributes, :colorbar_title, u)
x, y, z
end

# Recipe for vectors of vectors
@recipe function f(::Type{T}, x::T) where T <: AbstractVector{<:AbstractVector{<:Union{Missing,<:Quantity}}}
@recipe function f(::Type{T}, x::T) where T <: AbstractVector{<:AbstractVector{<:Union{Missing,<:Quantity, <:LogScaled}}}
axisletter = plotattributes[:letter] # x, y, or z
[fixaxis!(plotattributes, x, axisletter) for x in x]
end

# Recipe for bare units
@recipe function f(::Type{T}, x::T) where T <: Units
# Recipe for bare Union{Units, MixedUnits}
@recipe function f(::Type{T}, x::T) where T <: Union{Units, MixedUnits}
primary := false
Float64[]*x
end

# Recipes for functions
@recipe function f(f::Function, x::T) where T <: AVec{<:Union{Missing,<:Quantity}}
@recipe function f(f::Function, x::T) where T <: AVec{<:Union{Missing,<:Quantity,<:LogScaled}}
x, f.(x)
end
@recipe function f(x::T, f::Function) where T <: AVec{<:Union{Missing,<:Quantity}}
@recipe function f(x::T, f::Function) where T <: AVec{<:Union{Missing,<:Quantity,<:LogScaled}}
x, f.(x)
end
@recipe function f(x::T, y::AVec, f::Function) where T <: AVec{<:Union{Missing,<:Quantity}}
@recipe function f(x::T, y::AVec, f::Function) where T <: AVec{<:Union{Missing,<:Quantity,<:LogScaled}}
x, y, f.(x',y)
end
@recipe function f(x::AVec, y::T, f::Function) where T <: AVec{<:Union{Missing,<:Quantity}}
@recipe function f(x::AVec, y::T, f::Function) where T <: AVec{<:Union{Missing,<:Quantity,<:LogScaled}}
x, y, f.(x',y)
end
@recipe function f(x::T1, y::T2, f::Function) where {T1<:AVec{<:Union{Missing,<:Quantity}}, T2<:AVec{<:Union{Missing,<:Quantity}}}
@recipe function f(x::T1, y::T2, f::Function) where {T1<:AVec{<:Union{Missing,<:Quantity,<:LogScaled}}, T2<:AVec{<:Union{Missing,<:Quantity,<:LogScaled}}}
x, y, f.(x',y)
end
@recipe function f(f::Function, u::Units)
@recipe function f(f::Function, u::Union{Units, MixedUnits})
uf = UnitFunction(f, [u])
recipedata = RecipesBase.apply_recipe(plotattributes, uf)
(_, xmin, xmax) = recipedata[1].args
Expand All @@ -117,7 +119,7 @@ uf(3, 2) == f(3u"m", 2u"m"^2) == 7u"m^2"
"""
struct UnitFunction <: Function
f::Function
u::Vector{Units}
u::Vector{T} where T<: Union{Units, MixedUnits}
end
(f::UnitFunction)(args...) = f.f((args .* f.u)...)

Expand All @@ -138,7 +140,7 @@ fixlinecolor!(attr) = ustripattribute!(attr, :line_z)
function ustripattribute!(attr, key)
if haskey(attr, key)
v = attr[key]
u = unit(eltype(v))
u = fullunit(eltype(v))
attr[key] = ustrip.(u, v)
return u
else
Expand Down Expand Up @@ -198,7 +200,7 @@ end
Append unit to labels when appropriate
=====================================#

function append_unit_if_needed!(attr, key, u::Unitful.Units)
function append_unit_if_needed!(attr, key, u::T) where T<:Union{MixedUnits, Units}
label = get(attr, key, nothing)
append_unit_if_needed!(attr, key, label, u)
end
Expand Down
61 changes: 61 additions & 0 deletions src/UnitfulWrapper.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Unitful: Gain, Level, MixedUnits, LogScaled, NoUnits, uconvert
import Unitful: ustrip
export fullunit

#=====
Extension to Unitful.jl, required to plot LogScaled units and
Quantities with LogScaled values (dB/Hz).

Intruduces function:
fullunit

and adds a new method to ustrip for LogScaled units.

=====#

#=====
fullunit(x)
------------
Define new function fullunit which returns correct unit of Quantity and LogScaled.

Unitful.unit(dB/Hz) == Hz⁻¹
Unitful.unit(dB) == NoUnits
Unitful.logunit(dB/Hz) == dB

expected behavior:
fullunit(dB/Hz) == dB Hz⁻¹
fullunit(dB) == dB
fullunit(Hz) == Hz
=====#

# general argument
fullunit(x::T) where T<: Quantity = unit(x)
fullunit(x::Type{T}) where T<:Quantity = unit(x)

# gain
fullunit(x::Gain{L,S}) where {L,S} = fullunit(eltype(x))
fullunit(::Type{T}) where {L,S,T<:Gain{L,S}} = MixedUnits{Gain{L,S}}()

# level
fullunit(x::Level{L,S}) where {L,S} = fullunit(eltype(x))
fullunit(::Type{T}) where {L,S,T<:Level{L,S}} = MixedUnits{Level{L,S}}()

# quantity with mixed unit
fullunit(x::Quantity{T,D,U}) where {T<:LogScaled , D , U} = fullunit(eltype(x))
fullunit(::Type{Quantity{T, D, U}}) where {T<:LogScaled , D , U} = fullunit(T) * U()

# others from Unitful util.jl
fullunit(x::T) where T<: Number = NoUnits
fullunit(::Type{T}) where T<:Number= NoUnits
fullunit(::Type{Union{Missing, T}}) where T = fullunit(T)
fullunit(::Type{Missing}) = missing
fullunit(x::Missing) = missing


#=====
ustrip(u, x) for logscaled units
=====#
# level
function ustrip(u::T, x::L) where {T<:MixedUnits, L<:Union{Quantity, LogScaled}}
ustrip(uconvert(u, x))
end
61 changes: 61 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Test, Unitful, Plots
using Unitful: m, s, cm, DimensionError
using Unitful: dBm, dB, dBV, V, Hz, B, MHz
using UnitfulRecipes

# Some helper functions to access the subplot labels and the series inside each test plot
Expand Down Expand Up @@ -299,3 +300,63 @@ end
plot!(plt, (1:3)m)
@test yguide(plt) == "m"
end

@testset "LogScaled plots" begin
x, y, dbv, v = randn(3)*dBm, randn(3)*dB, rand(3)*dBV, rand(3)V

@testset "no keyword argument" begin
@test xguide(plot(x,y)) == "dBm"
@test xseries(plot(x,y)) ≈ ustrip.(x)
@test yguide(plot(x,y)) == "dB"
@test yseries(plot(x,y)) ≈ ustrip.(y)
plot(x, dbv)
@test yseries(plot!(x, v)) ≈ ustrip(uconvert.(u"dBV", v))
plot(x, v)
@test yseries(plot!(x, dbv)) ≈ ustrip(uconvert.(u"V", dbv))
end

@testset "labels" begin
@test xguide(plot(x, y, xlabel= "hello")) == "hello (dBm)"
@test xguide(plot(x, y, xlabel=P"hello")) == "hello"
@test yguide(plot(x, y, ylabel= "hello")) == "hello (dB)"
@test yguide(plot(x, y, ylabel=P"hello")) == "hello"
@test xguide(plot(x, y, xlabel= "hello", ylabel= "hello")) == "hello (dBm)"
@test xguide(plot(x, y, xlabel=P"hello", ylabel=P"hello")) == "hello"
@test yguide(plot(x, y, xlabel= "hello", ylabel= "hello")) == "hello (dB)"
@test yguide(plot(x, y, xlabel=P"hello", ylabel=P"hello")) == "hello"
end
end

@testset "mixed Log units" begin
x, y, x1, y1 = randn(3)*dB/Hz, randn(3)*dB*m, rand(3)*B/MHz, rand(3)*B*cm

@testset "no keyword argument" begin
dbhz = Sys.isapple() ? "dB Hz⁻¹" : "dB Hz^-1" # expect fancy exponent or not?
@test xguide(plot(x,y)) == dbhz
@test xseries(plot(x,y)) ≈ ustrip.(x)
@test yguide(plot(x,y)) == "dB m"
@test yseries(plot(x,y)) ≈ ustrip.(y)
end
@testset "plot!" begin
plot(x, y)
@test xseries(plot!(x1, y)) ≈ ustrip(ustrip(uconvert.(u"dB/Hz", x1)))
@test yseries(plot!(x1, y1)) ≈ ustrip(ustrip(uconvert.(u"dB*m", y1)))
end

end

@testset "fullunit methods test" begin
@test @inferred(fullunit(1m^2)) === m^2
@test @inferred(fullunit(1dB)) === dB
@test @inferred(fullunit(1dBm)) === dBm
@test @inferred(fullunit(1dB*m)) === dB*m
@test @inferred(fullunit(typeof(1m^2))) === m^2
@test @inferred(fullunit(typeof(1dB))) === dB
@test @inferred(fullunit(typeof(1dBm))) === dBm
@test @inferred(fullunit(typeof(1dB*m))) === dB*m
@test @inferred(fullunit(Float64)) === NoUnits
@test @inferred(fullunit(Union{typeof(1m^2),Missing})) === m^2
@test @inferred(fullunit(Union{Float64,Missing})) === NoUnits
@test @inferred(fullunit(missing)) === missing
@test @inferred(fullunit(Missing)) === missing
end