diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml index 259afaf..899cd68 100644 --- a/.JuliaFormatter.toml +++ b/.JuliaFormatter.toml @@ -1,4 +1,3 @@ -always_for_in = true import_to_using = false align_pair_arrow = true align_assignment = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d2185b..aae56a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: matrix: version: - '1.6' # LTS (lowest supported `julia` version declared in `Project.toml`) - - '1.8' # latest stable + - '1' # latest stable os: [ubuntu-latest, windows-latest, macos-latest] arch: [x64, x86] exclude: diff --git a/Project.toml b/Project.toml index 529905c..b3b8b48 100644 --- a/Project.toml +++ b/Project.toml @@ -13,17 +13,20 @@ PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] -PrecompileTools = "1" -ColorSchemes = "3.19" +ColorSchemes = "≥3.19" Colors = "0.12" +Dates = "<0.0.1, 1" +Printf = "<0.0.1, 1" +Random = "<0.0.1, 1" Reexport = "1" +PrecompileTools = "1" +Statistics = "<0.0.1, 1" julia = "1.6" [extras] Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" -Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Pkg", "StableRNGs", "Statistics", "Test"] +test = ["Pkg", "StableRNGs", "Test"] diff --git a/src/PlotUtils.jl b/src/PlotUtils.jl index 35bbee8..e2b7161 100644 --- a/src/PlotUtils.jl +++ b/src/PlotUtils.jl @@ -40,7 +40,7 @@ const _default_colorscheme = generate_colorscheme() if VERSION ≥ v"1.8.0" @compile_workload begin - for T in (Int, Float64) + for T ∈ (Int, Float64) optimize_ticks(-one(T), one(T)) optimize_ticks(-one(T), one(T); k_min = 2, k_max = 10) adapted_grid(sin, (-one(T), one(T))) diff --git a/src/adapted_grid.jl b/src/adapted_grid.jl index 7446307..b42743c 100644 --- a/src/adapted_grid.jl +++ b/src/adapted_grid.jl @@ -36,7 +36,7 @@ function adapted_grid( # Wiggle interior points a bit to prevent aliasing and other degenerate cases rng = MersenneTwister(1337) rand_factor = 0.05 - for i in 2:(length(xs) - 1) + for i ∈ 2:(length(xs) - 1) xs[i] += 2rand_factor * (rand(rng) - 0.5) * (xs[i + 1] - xs[i - 1]) end @@ -67,7 +67,7 @@ function adapted_grid( # Guard against division by zero later (f_range == 0 || !isfinite(f_range)) && (f_range = one(f_range)) # Skip first and last interval - for interval in 1:n_intervals + for interval ∈ 1:n_intervals p = 2interval if n_tot_refinements[interval] ≥ max_recursions # Skip intervals that have been refined too much @@ -77,7 +77,7 @@ function adapted_grid( else tot_w = 0.0 # Do a small convolution - for (q, w) in ((-1, 0.25), (0, 0.5), (1, 0.25)) + for (q, w) ∈ ((-1, 0.25), (0, 0.5), (1, 0.25)) interval == 1 && q == -1 && continue interval == n_intervals && q == 1 && continue tot_w += w @@ -99,7 +99,7 @@ function adapted_grid( end end # Approximate end intervals as being the same curvature as those next to it. - # This avoids computing the function in the end points + # This avoids computing the function ∈ the end points curvatures[1] = curvatures[2] active[1] = active[2] curvatures[end] = curvatures[end - 1] @@ -121,10 +121,10 @@ function adapted_grid( new_fs = zeros(eltype(fs), n_points + n_new_points) new_tot_refinements = zeros(Int, n_intervals + n_intervals_to_refine) k = kk = 0 - for i in 1:n_points - if iseven(i) # This is a point in an interval + for i ∈ 1:n_points + if iseven(i) # This is a point ∈ an interval interval = i ÷ 2 - if interval in intervals_to_refine + if interval ∈ intervals_to_refine kk += 1 new_tot_refinements[interval - 1 + kk] = n_tot_refinements[interval] + 1 new_tot_refinements[interval + kk] = n_tot_refinements[interval] + 1 @@ -170,10 +170,10 @@ Tries to call the callable `F` (which must accept one real argument) and determine when it executes without error. If `F` is an `AbstractArray`, it will find the first element of `vec` -for which all callables in `F` execute. +for which all callables ∈ `F` execute. """ function tryrange(F, vec) - for v in vec + for v ∈ vec try tmp = F(v) return v @@ -186,8 +186,8 @@ end # try some intervals over which the function may be defined function tryrange(F::AbstractArray, vec) - rets = [tryrange(f, vec) for f in F] # get the preferred for each + rets = [tryrange(f, vec) for f ∈ F] # get the preferred for each maxind = maximum(indexin(rets, vec)) # get the last attempt that succeeded (most likely to fit all) - rets .= [tryrange(f, vec[maxind:maxind]) for f in F] # ensure that all functions compute there + rets .= [tryrange(f, vec[maxind:maxind]) for f ∈ F] # ensure that all functions compute there rets[1] end diff --git a/src/color_utils.jl b/src/color_utils.jl index aca4640..806f3e1 100644 --- a/src/color_utils.jl +++ b/src/color_utils.jl @@ -41,12 +41,12 @@ function getpctrange(n::Integer) n > 0 || error() n == 1 && return zeros(1) zs = [0.0, 1.0] - for i in 3:n + for i ∈ 3:n sorted = sort(zs) diffs = diff(sorted) widestj = 0 widest = 0.0 - for (j, d) in enumerate(diffs) + for (j, d) ∈ enumerate(diffs) if d > widest widest = d widestj = j @@ -61,7 +61,7 @@ function get_zvalues(n::Integer) offsets = getpctrange(ceil(Int, n / 4) + 1) / 4 offsets = vcat(offsets[1], offsets[3:end]) zvalues = Float64[] - for offset in offsets + for offset ∈ offsets append!(zvalues, offset .+ [0.0, 0.5, 0.25, 0.75]) end vcat(zvalues[1], 1.0, zvalues[2:(n - 1)]) @@ -85,7 +85,7 @@ isdark(c::Colorant)::Bool = lightness_level(c) < 0.5 islight(c::Colorant)::Bool = !isdark(c) Base.convert(::Type{RGB}, h::Unsigned) = - RGB([(x & 0x0000FF) / 0xFF for x in (h >> 16, h >> 8, h)]...) + RGB([(x & 0x0000FF) / 0xFF for x ∈ (h >> 16, h >> 8, h)]...) make255(x)::Int = round(Int, 255 * x) diff --git a/src/colors.jl b/src/colors.jl index 7d4bc73..60af433 100644 --- a/src/colors.jl +++ b/src/colors.jl @@ -14,7 +14,7 @@ plot_color(b::Val{false}) = invisible() plot_color(b::Bool) = plot_color(Val(b)) plot_color(::Nothing) = invisible() plot_color(c::Colorant) = convert(RGBA{Float64}, c) -# plot_color(cs::AbstractVector) = RGBA{Float64}[plot_color(c) for c in cs] +# plot_color(cs::AbstractVector) = RGBA{Float64}[plot_color(c) for c ∈ cs] # plot_color(cs::AbstractArray) = map(plot_color, cs) # no alpha override @@ -29,16 +29,16 @@ plot_color(s::Symbol, α::Number) = ( function plot_color(cs::AbstractArray) a = Array{RGBA{Float64}}(undef, size(cs)...) - for i in eachindex(cs) + for i ∈ eachindex(cs) a[i] = plot_color(cs[i]) end a end -# plot_color(cs::AbstractVector, α::Number) = RGBA{Float64}[plot_color(c,α) for c in cs] +# plot_color(cs::AbstractVector, α::Number) = RGBA{Float64}[plot_color(c,α) for c ∈ cs] function plot_color(cs::AbstractArray, α::Number) a = Array{RGBA{Float64}}(undef, size(cs)...) - for i in eachindex(cs) + for i ∈ eachindex(cs) a[i] = plot_color(cs[i], α) end a @@ -50,13 +50,13 @@ end # function plot_color(zs::AbstractVector{T}) where T<:Number # grad = cgrad() # zmin, zmax = extrema(zs) -# RGBA{Float64}[grad[(z-zmin)/(zmax-zmin)] for z in zs] +# RGBA{Float64}[grad[(z-zmin)/(zmax-zmin)] for z ∈ zs] # end function plot_color(zs::AbstractArray{T}) where {T<:Number} grad = cgrad() zmin, zmax = extrema(zs[isfinite.(zs)]) a = Array{RGBA{Float64}}(undef, size(zs)...) - for i in eachindex(zs) + for i ∈ eachindex(zs) a[i] = grad[(zs[i] - zmin) / (zmax - zmin)] end a @@ -64,12 +64,12 @@ end # function plot_color(zs::AbstractVector{T}, α::Number) where T<:Number # cs = plot_color(zs) -# RGBA{Float64}[RGBA{Float64}(convert(RGB, c), α) for c in cs] +# RGBA{Float64}[RGBA{Float64}(convert(RGB, c), α) for c ∈ cs] # end function plot_color(zs::AbstractArray{T}, α::Number) where {T<:Number} cs = plot_color(zs) a = Array{RGBA{Float64}}(undef, size(zs)...) - for i in eachindex(zs) + for i ∈ eachindex(zs) a[i] = RGBA{Float64}(convert(RGB, cs[i]), α) end a diff --git a/src/colorschemes.jl b/src/colorschemes.jl index 9e80254..8a44379 100644 --- a/src/colorschemes.jl +++ b/src/colorschemes.jl @@ -25,7 +25,7 @@ abstract type ColorGradient <: AbstractColorList end Base.getindex(cg::ColorGradient, x::Union{AbstractFloat,AbstractVector{<:AbstractFloat}}) = get(cg, x) function Base.get(cg::ColorGradient, v::AbstractArray, rangescale = (0.0, 1.0)) - rangescale === :extrema && (rangescale = extrema(v)) + rangescale ≡ :extrema && (rangescale = extrema(v)) map(x -> get(cg, x, rangescale), v) end @@ -53,7 +53,7 @@ Base.show(io::IO, m::MIME"image/svg+xml", cg::ContinuousColorGradient) = function sample_color(cg::ContinuousColorGradient, x::AbstractFloat) c, v = cg.colors, cg.values - if (index = findfirst(==(x), v)) === nothing + if (index = findfirst(==(x), v)) ≡ nothing nm1 = length(v) - 1 i = min(nm1, findlast(<(x), v)) r = (x - v[i]) / (v[i + 1] - v[i]) @@ -81,7 +81,7 @@ function ColorSchemes.getinverse(cg::ContinuousColorGradient, c) alpha(c) == 0 && return NaN z = getinverse(to_rgb(get_colorscheme(cg)), to_rgb(c)) cr = get_range(cg.colors) - if (index = findfirst(==(z), cr)) !== nothing + if (index = findfirst(==(z), cr)) ≢ nothing cg.values[index] else i = min(length(cr) - 1, findlast(<(z), cr)) @@ -91,15 +91,15 @@ end function prepare_continuous_cgrad_colors(c, v) v = sort(unique(clamp.(v, 0, 1))) - 0 in v || pushfirst!(v, 0) - 1 in v || push!(v, 1) + 0 ∈ v || pushfirst!(v, 0) + 1 ∈ v || push!(v, 1) nv = length(v) nc = length(c) c, v = if nc != nv value_range = get_range(nv) color_range = get_range(nc) values = [0.0] - for i in 2:nv + for i ∈ 2:nv inds = findall(x -> value_range[i - 1] < x < value_range[i], color_range) isempty(inds) || append!( values, @@ -161,10 +161,12 @@ end function prepare_categorical_cgrad_colors(c, v) v = sort(unique(clamp.(v, 0, 1))) - 0 in v || pushfirst!(v, 0) - 1 in v || push!(v, 1) - c = ColorScheme(plot_color(c[get_range(length(v) - 1)])) - c, v + 0 ∈ v || pushfirst!(v, 0) + 1 ∈ v || push!(v, 1) + colors = map(c[get_range(length(v) - 1)]) do col + RGBA(RGB(col), clamp(alpha(col), 0, 1)) + end + ColorScheme(plot_color(colors)), v end ## cgrad @@ -193,22 +195,22 @@ function cgrad( rev = false, alpha = nothing, ) - if categorical !== nothing && categorical + if categorical ≢ nothing && categorical colors, values = prepare_categorical_cgrad_colors(colors, values) end - if alpha !== nothing + if alpha ≢ nothing rgbs = convert.(RGB, colors.colors) colors = ColorScheme(RGBA.(rgbs, alpha)) end rev && (colors = reverse(colors)) - values = if scale in (:log, :log10) || scale isa typeof(log10) + values = if scale ∈ (:log, :log10) || scale ≡ log10 log10.(ColorSchemes.remap(values, 0, 1, 1, 10)) - elseif scale === :log2 || scale isa typeof(log2) + elseif scale ≡ :log2 || scale ≡ log2 log2.(ColorSchemes.remap(values, 0, 1, 1, 2)) - elseif scale === :ln || scale isa typeof(log) + elseif scale ≡ :ln || scale ≡ log log.(ColorSchemes.remap(values, 0, 1, 1, ℯ)) - elseif scale in (:exp, :exp10) || scale isa typeof(exp10) || scale isa typeof(exp) + elseif scale ∈ (:exp, :exp10) || scale ≡ exp10 || scale ≡ exp ColorSchemes.remap(exp10.(values), 1, 10, 0, 1) elseif scale isa Function v = scale.(values) @@ -217,7 +219,7 @@ function cgrad( values end - if categorical !== nothing && categorical + if categorical ≢ nothing && categorical CategoricalColorGradient(colors, values) else ContinuousColorGradient(colors, values) @@ -230,12 +232,12 @@ function cgrad( categorical = nothing, kwargs..., ) - values = get_range(n + (categorical !== nothing)) + values = get_range(n + (categorical ≢ nothing)) cgrad(colors, values; categorical = categorical, kwargs...) end function cgrad(colors, args...; kwargs...) - colors === :default && (colors = :inferno) + colors ≡ :default && (colors = :inferno) cgrad(get_colorscheme(colors), args...; kwargs...) end @@ -244,8 +246,8 @@ cgrad(; kw...) = cgrad(DEFAULT_COLOR_GRADIENT[]; kw...) default_cgrad(cg; kw...) = DEFAULT_COLOR_GRADIENT[] = cgrad(cg; kw...) function get_rangescale(rangescale) - rangescale === :clamp && return (0.0, 1.0) - rangescale === :extrema && return extrema(x) + rangescale ≡ :clamp && return (0.0, 1.0) + rangescale ≡ :extrema && return extrema(x) (rangescale isa NTuple{2,Number}) || error( "rangescale ($rangescale) not supported, should be :clamp, :extrema or tuple (minVal, maxVal). Got $(rangescale).", ) @@ -272,7 +274,7 @@ If `rev` is `true` colors are reversed. """ function palette(cs; rev = false, alpha = nothing) cs = get_colorscheme(cs) - if alpha !== nothing + if alpha ≢ nothing rgbs = convert.(RGB, cs.colors) cs = ColorScheme(RGBA.(rgbs, alpha)) end @@ -288,7 +290,7 @@ end ## Utils -get_range(n::Int) = range(0, stop = 1, length = n) +get_range(n::Int) = range(0, stop = n == 1 ? 0 : 1, length = n) get_range(cs) = get_range(length(cs)) get_colorscheme(v::AbstractVector{<:Colorant}) = ColorScheme(v) @@ -296,7 +298,7 @@ get_colorscheme(v::AbstractVector) = ColorScheme(parse.(Colorant, v)) function get_colorscheme(sym::Symbol) haskey(MISC_COLORSCHEMES, sym) && return MISC_COLORSCHEMES[sym] sym = get(COLORSCHEME_ALIASES, sym, sym) - if sym === :default || sym === :auto + if sym ≡ :default || sym ≡ :auto _default_colorscheme elseif haskey(ColorSchemes.colorschemes, sym) ColorSchemes.colorschemes[sym] @@ -310,7 +312,7 @@ get_colorscheme(cs::ColorScheme) = cs function cvec(cs, n = 10; kw...) cg = cgrad(cs; kw...) - RGBA{Float64}[cg[z] for z in get_range(n)] + RGBA{Float64}[cg[z] for z ∈ get_range(n)] end color_list(c) = get_colorscheme(c).colors @@ -329,9 +331,9 @@ rgba_string(cg::T) where {T<:Union{ColorScheme,ColorGradient,ColorPalette}} = rgba_string(cg[1]) is_colorscheme(sym) = - sym in keys(ColorSchemes.colorschemes) || - sym in keys(COLORSCHEME_ALIASES) || - sym in keys(MISC_COLORSCHEMES) + sym ∈ keys(ColorSchemes.colorschemes) || + sym ∈ keys(COLORSCHEME_ALIASES) || + sym ∈ keys(MISC_COLORSCHEMES) const DEFAULT_COLOR_GRADIENT = Ref(cgrad(ColorSchemes.colorschemes[:inferno])) diff --git a/src/intervals.jl b/src/intervals.jl index e6d77b0..501a4fe 100644 --- a/src/intervals.jl +++ b/src/intervals.jl @@ -112,7 +112,7 @@ function zscale( local β # line slope # iteratively fit samples and reject sigma-clipped outliers - for _ in 1:max_iterations + for _ ∈ 1:max_iterations (ngood ≥ last_good || ngood < min_pix) && break # linear fit using mask @@ -156,7 +156,7 @@ function dilate_mask(mask, ngrow) idxs = CartesianIndices(mask) mindx = idxs[1].I[1] maxdx = idxs[end].I[1] - @inbounds for idx in idxs + @inbounds for idx ∈ idxs lower = max(mindx, idx.I[1] - ngrow) upper = min(maxdx, idx.I[1] + ngrow) # output will only be true if there are no falses in the input section diff --git a/src/ticks.jl b/src/ticks.jl index 19e899a..cfb0ed2 100644 --- a/src/ticks.jl +++ b/src/ticks.jl @@ -30,31 +30,33 @@ function bounding_order_of_magnitude(xspan::T, base::T) where {T} end function postdecimal_digits(x::T) where {T} - for i in floor(Int, log10(floatmin(T))):ceil(Int, log10(floatmax(T))) + for i ∈ floor(Int, log10(floatmin(T))):ceil(Int, log10(floatmax(T))) x == floor(x; digits = i) && return i end return 0 end -fallback_ticks(x_min::T, x_max::T, k_min, k_max) where {T} = ( +function fallback_ticks(x_min::T, x_max::T, k_min, k_max, strict_span) where {T} + if !strict_span && x_min ≈ x_max + x_min, x_max = prevfloat(x_min), nextfloat(x_max) + end if k_min != 2 && isfinite(x_min) && isfinite(x_max) collect(T, range(x_min, x_max; length = k_min)), x_min, x_max else T[x_min, x_max], x_min, x_max end -) +end # Empty catchall optimize_ticks() = Any[] """ - optimize_ticks(xmin, xmax; extend_ticks::Bool=false, - Q=[(1.0,1.0), (5.0, 0.9), (2.0, 0.7), (2.5, 0.5), (3.0, 0.2)], - k_min::Int=2, k_max::Int=10, k_ideal::Int=5, - granularity_weight::Float64=1/4, simplicity_weight::Float64=1/6, - coverage_weight::Float64=1/3, niceness_weight::Float64=1/4, - strict_span=true, span_buffer = nothing - ) +optimize_ticks(xmin, xmax; extend_ticks::Bool = false, + Q = [(1.0,1.0), (5.0, 0.9), (2.0, 0.7), (2.5, 0.5), (3.0, 0.2)], + k_min = 2, k_max = 10, k_ideal = 5, + granularity_weight = 1/4, simplicity_weight = 1/6, + coverage_weight = 1/3, niceness_weight = 1/4, + strict_span = true, span_buffer = nothing) Find some reasonable values for tick marks. @@ -133,7 +135,7 @@ These components are defined as follows: and the variables here are: * `q`: element of `Q`. -* `i`: index of `q` in `Q`. +* `i`: index of `q` ∈ `Q`. * `v`: 1 if label range includes 0, 0 otherwise. """ function optimize_ticks( @@ -152,19 +154,17 @@ function optimize_ticks( span_buffer = nothing, scale = nothing, ) where {T} - F = float(T) - if x_max - x_min < eps(F) - return fallback_ticks(x_min, x_max, k_min, k_max) - end + x_min ≈ x_max && return fallback_ticks(x_min, x_max, k_min, k_max, strict_span) - Qv = F[q[1] for q in Q] - Qs = F[q[2] for q in Q] + F = float(T) + Qv = F[q[1] for q ∈ Q] + Qs = F[q[2] for q ∈ Q] base_float = F(get(_logScaleBases, scale, 10.0)) base = isinteger(base_float) ? Int(base_float) : 10 is_log_scale = scale ∈ _logScales - for i in 1:2 + for i ∈ 1:2 sspan = i == 1 ? strict_span : false high_score, best, min_best, max_best = optimize_ticks_typed( F(x_min), @@ -190,7 +190,7 @@ function optimize_ticks( if sspan @warn "No strict ticks found" else - return fallback_ticks(x_min, x_max, k_min, k_max) + return fallback_ticks(x_min, x_max, k_min, k_max, strict_span) end else return best, min_best, max_best @@ -226,7 +226,7 @@ function optimize_ticks_typed( # for q values specified in Qv num_digits = ( bounding_order_of_magnitude(max(abs(x_min), abs(x_max)), base_float) + - maximum(postdecimal_digits(q) for q in Qv) + maximum(postdecimal_digits(q) for q ∈ Qv) ) viewmin_best, viewmax_best = x_min, x_max @@ -240,8 +240,8 @@ function optimize_ticks_typed( @inbounds begin while 2k_max * base_float^(z + 1) > xspan sigdigits = max(1, num_digits - z) - for k in k_min:(2k_max) - for (q, qscore) in zip(Qv, Qs) + for k ∈ k_min:(2k_max) + for (q, qscore) ∈ zip(Qv, Qs) tickspan = q * base_float^z tickspan < eps(F) && continue span = (k - 1) * tickspan @@ -254,16 +254,16 @@ function optimize_ticks_typed( # try to favor integer exponents for log scales (nice_scale = !is_log_scale || isinteger(tickspan)) || (qscore = F(0)) - while r * tickspan <= x_min + while r * tickspan ≤ x_min # Filter or expand ticks if extend_ticks - for i in 0:(3k - 1) + for i ∈ 0:(3k - 1) S[i + 1] = (r + i - k) * tickspan end imin = k + 1 imax = 2k else - for i in 0:(k - 1) + for i ∈ 0:(k - 1) S[i + 1] = (r + i) * tickspan end imin = 1 @@ -286,8 +286,8 @@ function optimize_ticks_typed( # we do this because it saves allocations and leaves S type stable counter = 0 - for i in 1:imax - if (viewmin - buf) <= S[i] <= (viewmax + buf) + for i ∈ 1:imax + if (viewmin - buf) ≤ S[i] ≤ (viewmax + buf) counter += 1 S[counter] = S[i] end @@ -298,7 +298,7 @@ function optimize_ticks_typed( end # evaluate quality of ticks - has_zero = r <= 0 && abs(r) < k + has_zero = r ≤ 0 && abs(r) < k # simplicity s = has_zero && nice_scale ? 1 : 0 @@ -324,11 +324,11 @@ function optimize_ticks_typed( if strict_span && span > xspan score -= 10000 end - if span >= 2xspan + if span ≥ 2xspan score -= 1000 end - if score > high_score && (k_min <= len <= k_max) + if score > high_score && (k_min ≤ len ≤ k_max) viewmin_best, viewmax_best = viewmin, viewmax high_score, len_S_best = score, len copyto!(S_best, view(S, 1:len)) @@ -382,10 +382,8 @@ function optimize_ticks( x_max += Second(1) end - if year(x_max) - year(x_min) <= 1 && scale !== :year - if year(x_max) == year(x_min) && - month(x_max) - month(x_min) <= 1 && - scale !== :month + if year(x_max) - year(x_min) ≤ 1 && scale ≢ :year + if year(x_max) == year(x_min) && month(x_max) - month(x_min) ≤ 1 && scale ≢ :month ticks = DateTime[] scales = [ @@ -399,7 +397,7 @@ function optimize_ticks( ] # ticks on week boundries - if x_min + Day(7) < x_max || scale === :week + if x_min + Day(7) < x_max || scale ≡ :week push!(ticks, x_min) while true next_month = Date(year(ticks[end]), month(ticks[end])) + Month(1) @@ -407,18 +405,18 @@ function optimize_ticks( push!(ticks, ticks[end] + Week(1)) end push!(ticks, next_month) - if next_month >= x_max + if next_month ≥ x_max break end end else scale = nothing - if scale !== :auto + if scale ≢ :auto # TODO: manually setting scale with :day, :minute, etc end - if scale === nothing - for proposed_scale in [ + if scale ≡ nothing + for proposed_scale ∈ [ Day(1), Hour(1), Minute(1), @@ -434,18 +432,18 @@ function optimize_ticks( end end - if scale === nothing + if scale ≡ nothing scale = Millisecond(1) end # round x_min down - first_tick = if scale === Day(1) + first_tick = if scale ≡ Day(1) DateTime(year(x_min), month(x_min), day(x_min)) - elseif scale === Hour(1) + elseif scale ≡ Hour(1) DateTime(year(x_min), month(x_min), day(x_min), hour(x_min)) - elseif scale === Minute(1) + elseif scale ≡ Minute(1) DateTime(year(x_min), month(x_min), day(x_min), hour(x_min), minute(x_min)) - elseif scale === Second(1) + elseif scale ≡ Second(1) DateTime( year(x_min), month(x_min), @@ -454,7 +452,7 @@ function optimize_ticks( minute(x_min), second(x_min), ) - elseif scale === Millisecond(100) + elseif scale ≡ Millisecond(100) DateTime( year(x_min), month(x_min), @@ -464,7 +462,7 @@ function optimize_ticks( second(x_min), millisecond(x_min) % 100, ) - elseif scale === Millisecond(10) + elseif scale ≡ Millisecond(10) DateTime( year(x_min), month(x_min), @@ -503,7 +501,7 @@ function optimize_ticks( extend_ticks = extend_ticks, ) - return DateTime[DateTime(round(y)) for y in ticks], + return DateTime[DateTime(round(y)) for y ∈ ticks], DateTime(round(viewmin)), DateTime(round(viewmax)) end @@ -512,7 +510,7 @@ end # Generate ticks suitable for multiple scales. function multilevel_ticks(viewmin::T, viewmax::T; scales = [0.5, 5.0, 10.0]) where {T} ticks = Dict() - for scale in scales + for scale ∈ scales ticks[scale] = optimize_ticks( viewmin, viewmax, @@ -539,10 +537,10 @@ function multilevel_ticks( # TODO: This needs to be improved for DateTime span = convert(Float64, Dates.toms(viewmax - viewmin)) ticks = Dict() - for scale in scales - s = if scale === :year + for scale ∈ scales + s = if scale ≡ :year span / Dates.toms(Day(360)) - elseif scale === :month + elseif scale ≡ :month span / Dates.toms(Day(90)) else span / Dates.toms(Day(1)) @@ -596,8 +594,8 @@ function optimize_datetime_ticks(a_min, a_max; k_min = 2, k_max = 4) Dates.Year, ] i = findfirst(period_hierarchy .== P) - showtype = i >= 5 ? Date : DateTime - start = DateTime((PH(x_min) for PH in period_hierarchy[end:-1:i])...) + P(1) + showtype = i ≥ 5 ? Date : DateTime + start = DateTime((PH(x_min) for PH ∈ period_hierarchy[end:-1:i])...) + P(1) ticks = collect(start:steplength:x_max) labels = string.(showtype.(ticks)) diff --git a/test/adaptive_test_functions.jl b/test/adaptive_test_functions.jl index e2b3471..7e03b6c 100644 --- a/test/adaptive_test_functions.jl +++ b/test/adaptive_test_functions.jl @@ -26,7 +26,7 @@ const test_funcs = [ ] main() = begin - plots = [plot(func...) for func in test_funcs] + plots = [plot(func...) for func ∈ test_funcs] pitchfork = plot(x -> sqrt(x), 0, 10) plot!(pitchfork, x -> -sqrt(x), 0, 10) @@ -38,7 +38,7 @@ main() = begin m = ceil(Int, √(np)) + 1 n, r = divrem(np, m) r == 0 || (n += 1) - append!(plots, [plot() for _ in 1:(m * n - np)]) + append!(plots, [plot() for _ ∈ 1:(m * n - np)]) @assert length(plots) == m * n diff --git a/test/downstream.jl b/test/downstream.jl index d62a747..702eaf1 100644 --- a/test/downstream.jl +++ b/test/downstream.jl @@ -1,47 +1,100 @@ -using Pkg, PlotUtils +using Pkg, PlotUtils, Test LibGit2 = Pkg.GitTools.LibGit2 TOML = Pkg.TOML -Plots_jl = joinpath(mkpath(tempname()), "Plots.jl") -Plots_toml = joinpath(Plots_jl, "Project.toml") - -# clone and checkout the latest stable version of Plots -depot = joinpath(first(DEPOT_PATH), "registries", "General", "P", "Plots", "Versions.toml") -stable = maximum(VersionNumber.(keys(TOML.parse(read(depot, String))))) -for i in 1:6 - try - global repo = Pkg.GitTools.ensure_clone( - stdout, - Plots_jl, - "https://github.com/JuliaPlots/Plots.jl", - ) - break - catch err - @warn err - sleep(20i) +failsafe_clone_checkout(path, url) = begin + local repo + for i ∈ 1:6 + try + repo = Pkg.GitTools.ensure_clone(stdout, path, url) + break + catch err + @warn err + sleep(20i) + end end + + @assert isfile(joinpath(path, "Project.toml")) "spurious network error: clone failed, bailing out" + + name, _ = splitext(basename(url)) + registries = joinpath(first(DEPOT_PATH), "registries") + general = joinpath(registries, "General") + versions = joinpath(general, name[1:1], name, "Versions.toml") + if !isfile(versions) + mkpath(general) + run(setenv(`tar xf $general.tar.gz`; dir = general)) + end + @assert isfile(versions) + + stable = maximum(VersionNumber.(keys(TOML.parse(read(versions, String))))) + tag = LibGit2.GitObject(repo, "v$stable") + hash = string(LibGit2.target(tag)) + LibGit2.checkout!(repo, hash) + nothing +end + +fake_supported_version!(path) = begin + toml = joinpath(path, "Project.toml") + # fake the supported PlotUtils version for testing (for `Pkg.develop`) + PlotUtils_version = + Pkg.Types.read_package(normpath(@__DIR__, "..", "Project.toml")).version + parsed_toml = TOML.parse(read(toml, String)) + parsed_toml["compat"]["PlotUtils"] = string(PlotUtils_version) + open(toml, "w") do io + TOML.print(io, parsed_toml) + end + nothing end -@assert isfile(Plots_toml) "spurious network error: clone failed, bailing out" -tag = LibGit2.GitObject(repo, "v$stable") -hash = string(LibGit2.target(tag)) -LibGit2.checkout!(repo, hash) - -# fake the supported PlotUtils version for testing (for `Pkg.develop`) -plotutils_version = Pkg.Types.read_package(normpath(@__DIR__, "..", "Project.toml")).version -toml = TOML.parse(read(Plots_toml, String)) -toml["compat"]["PlotUtils"] = string(plotutils_version) -open(Plots_toml, "w") do io - TOML.print(io, toml) + +develop_stable_Plots() = begin + tmpd = mktempdir() + Plots_jl = joinpath(tmpd, "Plots.jl") + + failsafe_clone_checkout(Plots_jl, "https://github.com/JuliaPlots/Plots.jl") + fake_supported_version!(Plots_jl) + + Pkg.develop(path = Plots_jl) + Pkg.status(["PlotUtils", "Plots"]) + nothing +end + +develop_stable_Makie(extended = false) = begin + tmpd = mktempdir() + Makie_jl = joinpath(tmpd, "Makie.jl") + + failsafe_clone_checkout(Makie_jl, "https://github.com/MakieOrg/Makie.jl") + fake_supported_version!(Makie_jl) + + Pkg.develop(path = joinpath(tmpd, "Makie.jl", "MakieCore")) + Pkg.develop(path = joinpath(tmpd, "Makie.jl")) + if extended # too costly ? + Pkg.develop(path = joinpath(tmpd, "Makie.jl", "ReferenceTests")) + Pkg.develop(path = joinpath(tmpd, "Makie.jl", "CairoMakie")) + # Pkg.develop(path = joinpath(tmpd, "Makie.jl", "GLMakie")) + end + Pkg.status(["PlotUtils", "MakieCore", "Makie"]) + nothing +end + +develop_stable_Plots() +using Plots + +@testset "downstream Plots" begin + # test basic plots creation & display (Plots tests are too long to run) + withenv("GKSwstype" => "nul") do + @time for i ∈ 1:length(Plots._examples) + i ∈ Plots._backend_skips[:gr] && continue # skip unsupported examples + Plots._examples[i].imports ≡ nothing || continue # skip examples requiring optional test deps + show(devnull, Plots.test_examples(:gr, i; disp = false)) # trigger display logic + end + end end -Pkg.develop(path = Plots_jl) -Pkg.status(["PlotUtils", "Plots"]) -# test basic plots creation & display -using Plots, Test +extended = tryparse(Bool, get(ENV, "CI", "false")) === true # extended test in CI -@time for i in 1:length(Plots._examples) - i ∈ Plots._backend_skips[:gr] && continue # skip unsupported examples - Plots._examples[i].imports ≡ nothing || continue # skip examples requiring optional test deps - show(devnull, Plots.test_examples(:gr, i; disp = false)) # trigger display logic +develop_stable_Makie(extended) +@testset "downstream Makie" begin + Pkg.test("Makie") + extended && Pkg.test("CairoMakie") end diff --git a/test/grid.png b/test/grid.png new file mode 100644 index 0000000..9825538 Binary files /dev/null and b/test/grid.png differ diff --git a/test/runtests.jl b/test/runtests.jl index ccc9a3f..551d822 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,9 @@ -using PlotUtils -using Test using Statistics: mean -using Dates -using Random using StableRNGs +using PlotUtils +using Random +using Dates +using Test rng = StableRNG(42) warn_ticks = :warn, "No strict ticks found" @@ -27,7 +27,7 @@ const C0 = RGBA{PlotUtils.Colors.N0f8} grad = cgrad() @test typeof(grad) == PlotUtils.ContinuousColorGradient - @test plot_color(grad) === grad + @test plot_color(grad) ≡ grad # JuliaPlots/Plots.jl/issues/4270 @test get(grad, BigFloat(1), (-0.0003354626279, 1.0)) isa RGBA{BigFloat} @@ -44,6 +44,7 @@ const C0 = RGBA{PlotUtils.Colors.N0f8} @test length(color_list(grad)) == 3 @test grad.values == [0, 0.1, 1] + Random.seed!(rng, 42) cs = plot_color(rand(rng, 10)) @test typeof(cs) == Vector{C} @test length(cs) == 10 @@ -56,7 +57,7 @@ const C0 = RGBA{PlotUtils.Colors.N0f8} cs = plot_color(rand(rng, 10), 0.5) @test typeof(cs) == Vector{C} @test length(cs) == 10 - for c in cs + for c ∈ cs @test alpha(c) == 0.5 end @@ -64,7 +65,7 @@ const C0 = RGBA{PlotUtils.Colors.N0f8} @test typeof(cs) == Matrix{C} @test length(cs) == 16 @test size(cs) == (4, 4) - for c in cs + for c ∈ cs @test alpha(c) == 0.5 end end @@ -108,9 +109,9 @@ end function test_ticks(x, y, ticks) @test issorted(ticks) - @test all(x .<= ticks .<= y) + @test all(x .≤ ticks .≤ y) if x < y - @test length(ticks) >= 2 + @test length(ticks) ≥ 2 @test is_uniformly_spaced(ticks) end end @@ -127,33 +128,33 @@ end end @testset "small range" begin - @testset "small range $x, $(i)ϵ" for x in exp10.(-12:12), i in -5:5 + @testset "small range $x, $(i)ϵ" for x ∈ exp10.(-12:12), i ∈ -5:5 y = x + i * eps(x) x, y = minmax(x, y) ticks, = optimize_ticks(x, y) @test issorted(ticks) - @test all(x .<= ticks .<= y) + @test all(x .≤ ticks .≤ y) # Fails: # @test allunique(ticks) end end @testset "fixed ranges" begin - @testset "fixed range $x..$y" for (x, y) in [(2, 14), (14, 25), (16, 36), (57, 69)] + @testset "fixed range $x..$y" for (x, y) ∈ [(2, 14), (14, 25), (16, 36), (57, 69)] test_ticks(+x, +y, optimize_ticks(+x, +y)[1]) test_ticks(-y, -x, optimize_ticks(-y, -x)[1]) end end @testset "random ranges" begin - r = [minmax(rand(rng, -100:100, 2)...) .* 10.0^i for _ in 1:10, i in -5:5] - @testset "random range $x..$y" for (x, y) in r + r = [minmax(rand(rng, -100:100, 2)...) .* 10.0^i for _ ∈ 1:10, i ∈ -5:5] + @testset "random range $x..$y" for (x, y) ∈ r test_ticks(x, y, optimize_ticks(x, y)[1]) end end @testset "digits" begin - @testset "digits $((10^n) - 1)*10^$i" for n in 1:9, i in -9:9 + @testset "digits $((10^n) - 1)*10^$i" for n ∈ 1:9, i ∈ -9:9 y0 = 10^n x0 = y0 - 1 x, y = (x0, y0) .* 10.0^i @@ -163,7 +164,7 @@ end end @testset "types" begin - for T in (Int32, Int64, Float16, Float32, Float64) + for T ∈ (Int32, Int64, Float16, Float32, Float64) x, y = T(1), T(10) ticks, = optimize_ticks(x, y) @test eltype(ticks) <: AbstractFloat @@ -185,7 +186,7 @@ end end @testset "PlotUtils.jl/issues/114" begin - let x = -0.1eps(), y = 0.1eps() + let x = -1.2eps(), y = 1.2eps() test_ticks(x, y, optimize_ticks(x, y)[1]) end end @@ -207,28 +208,40 @@ end let x = NaN, y = 1.0 ticks, = @test_logs warn_ticks optimize_ticks(x, y) @test isnan(ticks[1]) - @test ticks[2] === y + @test ticks[2] ≡ y ticks, = @test_logs warn_ticks optimize_ticks(x, y, k_min = 5) @test isnan(ticks[1]) - @test ticks[2] === y + @test ticks[2] ≡ y end let x = 0.0f0, y = Inf32 ticks, = @test_logs warn_ticks optimize_ticks(x, y) - @test ticks[1] === x + @test ticks[1] ≡ x @test isinf(ticks[2]) ticks, = @test_logs warn_ticks optimize_ticks(x, y, k_min = 5) - @test ticks[1] === x + @test ticks[1] ≡ x @test isinf(ticks[2]) end end end + + @testset "PlotUtils.jl/issues/155" begin + for n ∈ 1:10 + @test length(palette(:tab10, n)) == n + end + end + + @testset "PlotUtils.jl/issues/156" begin + for c ∈ cgrad([:black, RGBA{Float64}(1, 1, 1, 1)], 5; categorical = true) + @test 0 ≤ alpha(c) ≤ 1 + end + end end @testset "adapted grid" begin let f = sin, int = (0, π) xs, fs = adapted_grid(f, int) - for i in 1:(length(xs) - 1) - for λ in 0:0.1:1 + for i ∈ 1:(length(xs) - 1) + for λ ∈ 0:0.1:1 # test that `f` is well approximated by a line # in the interval `(xs[i], xs[i+1])` x = λ * xs[i] + (1 - λ) * xs[i + 1] @@ -262,14 +275,15 @@ end end @testset "zscale" begin - bkg = 30 .* randn(rng, 8192) .+ 1000 - data = bkg .+ 100 .* randn(rng, 8192) .+ 2500 + Random.seed!(rng, 42) + bkg = 30 .* randn(rng, 4_096) .+ 1_000 + data = bkg .+ 100 .* randn(rng, 4_096) .+ 2500 defects = rand(rng, CartesianIndices(bkg), 500) data[defects] .= rand(rng, [0, 1e7], 500) cmin, cmax = zscale(data) # values calculated using IRAF - @test cmin ≈ 2841.586 atol = 1e-3 - @test cmax ≈ 4171.648 atol = 1e-3 + @test cmin ≈ 2_784.824 atol = 1e-3 + @test cmax ≈ 4_211.375 atol = 1e-3 @test cmin > minimum(data) @test cmax < maximum(data) @@ -278,7 +292,7 @@ end @test cmin == 1 @test cmax == 100 - # Make sure output is finite + # make sure output is finite data = vcat(0:999, NaN) cmin, cmax = zscale(data) @test cmin == 0 @@ -287,19 +301,15 @@ end @testset "allocations" begin # see PlotUtils.jl/pull/136 stats = @timed optimize_ticks(0.1123, 100.132) - @test stats.bytes < 1_000 # ~ 736 (on 1.8) - @test stats.time < 1e-3 # ~ 0.56ms (on 1.8) + @test stats.bytes < 1_000 # ~ 736 (on 1.9) + @test stats.time < 1e-3 # ~ 0.22ms (on 1.9) end -if Sys.islinux() +if Sys.islinux() && VERSION ≥ v"1.9.0" && isempty(VERSION.prerelease) # avoid running on `nightly` @testset "downstream" begin - withenv("GKSwstype" => "nul") do - include("downstream.jl") - end - @test true + include("downstream.jl") end - - @testset "adaptive" begin + @testset "adaptive" begin # NOTE: must be ran after downstream test (for Plots) withenv("GKSwstype" => "nul") do include("adaptive_test_functions.jl") end