From 5796e9adfa99f925a534871ce38ffa985a9edc8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 13:18:20 -0300 Subject: [PATCH 01/16] First attempt at cleaning code --- ext/ColorfyCategoricalArraysExt.jl | 3 +- ext/ColorfyDistributionsExt.jl | 22 ++++--- ext/ColorfyUnitfulExt.jl | 7 +- src/Colorfy.jl | 100 +++++++++-------------------- test/runtests.jl | 16 ++--- 5 files changed, 56 insertions(+), 92 deletions(-) diff --git a/ext/ColorfyCategoricalArraysExt.jl b/ext/ColorfyCategoricalArraysExt.jl index 72a9df6..dcc2f99 100644 --- a/ext/ColorfyCategoricalArraysExt.jl +++ b/ext/ColorfyCategoricalArraysExt.jl @@ -14,7 +14,8 @@ function Colorfy.getcolors(colorfier::Colorfier{<:Values{CategoricalValue}}) colorscheme = Colorfy.colorscheme(colorfier) nlevels = length(levels(values)) categcolors = colorscheme[range(0, nlevels > 1 ? 1 : 0, length=nlevels)] - categcolors[levelcode.(values)] + colors = categcolors[levelcode.(values)] + coloralpha.(colors, alphas(colorfier)) end end diff --git a/ext/ColorfyDistributionsExt.jl b/ext/ColorfyDistributionsExt.jl index 596a20a..9ae3705 100644 --- a/ext/ColorfyDistributionsExt.jl +++ b/ext/ColorfyDistributionsExt.jl @@ -9,19 +9,23 @@ using Colorfy: Values using Distributions: Distribution, location, scale function Colorfy.getcolors(colorfier::Colorfier{<:Values{Distribution}}) - values = location.(Colorfy.values(colorfier)) - dcolorfier = Colorfy.update(colorfier; values) - Colorfy.getcolors(dcolorfier) -end - -function Colorfy.defaultalphas(values::Values{Distribution}) - s = scale.(values) + # extract location and scale parameters + v = Colorfy.values(colorfier) + m = location.(v) + s = scale.(v) a, b = extrema(s) - if a == b - fill(1, length(values)) + + # build new colorfier with location as values and alphas based on scale + values = m + alphas = if a == b + fill(1, length(v)) else @. 1 - (s - a) / (b - a) end + colorscheme = Colorfy.colorscheme(colorfier) + colorrange = Colorfy.colorrange(colorfier) + colorfier′ = Colorfier(values; alphas, colorscheme, colorrange) + Colorfy.getcolors(colorfier′) end end diff --git a/ext/ColorfyUnitfulExt.jl b/ext/ColorfyUnitfulExt.jl index 34639c3..785ed08 100644 --- a/ext/ColorfyUnitfulExt.jl +++ b/ext/ColorfyUnitfulExt.jl @@ -10,8 +10,11 @@ using Unitful: Quantity, ustrip function Colorfy.getcolors(colorfier::Colorfier{<:Values{Quantity}}) values = ustrip.(Colorfy.values(colorfier)) - ucolorfier = Colorfy.update(colorfier; values) - Colorfy.getcolors(ucolorfier) + alphas = Colorfy.alphas(colorfier) + colorscheme = Colorfy.colorscheme(colorfier) + colorrange = Colorfy.colorrange(colorfier) + colorfier′ = Colorfier(values; alphas, colorscheme, colorrange) + Colorfy.getcolors(colorfier′) end Colorfy.ascolorrange(colorrange::NTuple{2,Quantity}) = Colorfy.ascolorrange(ustrip.(colorrange)) diff --git a/src/Colorfy.jl b/src/Colorfy.jl index b4bd44c..37fb096 100644 --- a/src/Colorfy.jl +++ b/src/Colorfy.jl @@ -21,10 +21,10 @@ Maps each value in `values` to a color. Colors can be obtained using the [`Color ## Options -* `alphas` - Scalar or a vector of color alphas (default to `Colorfy.defaultalphas(values)`); -* `colorscheme` - Color scheme specification (default to `Colorfy.defaultcolorscheme(values)`); +* `alphas` - Scalar or a vector of color alphas (default to `1.0`); +* `colorscheme` - Color scheme specification (default to `:viridis`); * `colorrange` - Tuple with minimum and maximum color values or a symbol that can be passed - to the `rangescale` argument of the `ColorSchemes.get` function (default to `Colorfy.defaultcolorrange(values)`); + to the `rangescale` argument of the `ColorSchemes.get` function (default to `:extrema`); """ struct Colorfier{V,A,S,R} values::V @@ -33,12 +33,12 @@ struct Colorfier{V,A,S,R} colorrange::R end -function Colorfier(values; alphas=nothing, colorscheme=nothing, colorrange=nothing) +function Colorfier(values; alphas=1.0, colorscheme=:viridis, colorrange=:extrema) values′ = asvalues(values) - alphas′ = isnothing(alphas) ? defaultalphas(values′) : alphas - colorscheme′ = isnothing(colorscheme) ? defaultcolorscheme(values′) : colorscheme - colorrange′ = isnothing(colorrange) ? defaultcolorrange(values′) : colorrange - Colorfier(values′, asalphas(alphas′, values′), ascolorscheme(colorscheme′), ascolorrange(colorrange′)) + alphas′ = asalphas(alphas, values′) + colorscheme′ = ascolorscheme(colorscheme) + colorrange′ = ascolorrange(colorrange) + Colorfier(values′, alphas′, colorscheme′, colorrange′) end """ @@ -50,19 +50,6 @@ See also [`Colorfier`](@ref), [`Colorfy.colors`](@ref). """ colorfy(values; kwargs...) = colors(Colorfier(values; kwargs...)) -""" - Colorfy.update(colorfier; [values, alphas, colorscheme, colorrange]) - -Constructs a new colorfier with `colorfier` fields and updated fields passed as keyword arguments. -""" -update( - colorfier::Colorfier; - values=values(colorfier), - alphas=alphas(colorfier), - colorscheme=colorscheme(colorfier), - colorrange=colorrange(colorfier) -) = Colorfier(values; alphas, colorscheme, colorrange) - # -------- # GETTERS # -------- @@ -99,40 +86,6 @@ colorrange(colorfier::Colorfier) = colorfier.colorrange # API # ---- -""" - Colorfy.defaultalphas(values) - -Default color alphas for `values`. -""" -function defaultalphas(values::Values) - minds = findall(ismissing, values) - vinds = setdiff(1:length(values), minds) - - if isempty(minds) - fill(1, length(values)) - else - valphas = defaultalphas(nonmissingvec(values[vinds])) - malpha = zero(eltype(valphas)) - genvec(vinds, valphas, minds, malpha, length(values)) - end -end - -defaultalphas(values::Values{Colorant}) = alpha.(values) - -""" - Colorfy.defaultcolorscheme(values) - -Default color scheme for `values`. -""" -defaultcolorscheme(_) = colorschemes[:viridis] - -""" - Colorfy.defaultcolorrange(values) - -Default color range for `values`. -""" -defaultcolorrange(_) = :extrema - """ Colorfy.asvalues(values) @@ -150,7 +103,7 @@ asvalues(values::Values{Colorant{Q0f63}}) = fixcolors(values) Valid color alphas for a given `alphas` and `values`. """ -asalphas(alpha, values) = fill(alpha, length(values)) +asalphas(alpha::Number, values) = fill(alpha, length(values)) function asalphas(alphas::AbstractVector, values) if length(alphas) ≠ length(values) throw(ArgumentError("the number of alphas must be equal to the number of values")) @@ -189,16 +142,18 @@ function colors(colorfier::Colorfier) iinds = findall(isinvalid, vals) vinds = setdiff(1:length(vals), iinds) + # construct new colorfier with valid values only + vvalues = nonmissingvec(vals[vinds]) + valphas = alphas(colorfier)[vinds] + vcolorscheme = colorscheme(colorfier) + vcolorrange = colorrange(colorfier) + vcolorfier = Colorfier(vvalues; alphas=valphas, colorscheme=vcolorscheme, colorrange=vcolorrange) + if isempty(iinds) - # required to handle Vector{Union{Missing,T}} without missing values - vvals = nonmissingvec(vals) - vcolorfier = update(colorfier, values=vvals) - coloralpha.(getcolors(vcolorfier), alphas(vcolorfier)) + # all values are valid, so we can directly dispatch methods + getcolors(vcolorfier) else # get valid colors and set "transparent" for invalid values - vvals = nonmissingvec(vals[vinds]) - valphas = alphas(colorfier)[vinds] - vcolorfier = update(colorfier, values=vvals, alphas=valphas) vcolors = colors(vcolorfier) icolor = colorant"transparent" genvec(vinds, vcolors, iinds, icolor, length(vals)) @@ -208,8 +163,7 @@ end """ Colorfy.getcolors(colorfier, values) -Function intended for developers that returns the mapped colors from the `colorfier` without the alphas. -Alphas are applied in the `Colorfy.colors` function. +Function intended for developers that returns the mapped colors from the `colorfier`. """ function getcolors(colorfier::Colorfier) throw(ArgumentError(""" @@ -218,8 +172,10 @@ function getcolors(colorfier::Colorfier) """)) end -getcolors(colorfier::Colorfier{<:Values{Number}}) = - get(colorscheme(colorfier), values(colorfier), colorrange(colorfier)) +function getcolors(colorfier::Colorfier{<:Values{Number}}) + colors = get(colorscheme(colorfier), values(colorfier), colorrange(colorfier)) + coloralpha.(colors, alphas(colorfier)) +end getcolors(colorfier::Colorfier{<:Values{AbstractString}}) = parse.(Ref(Colorant), values(colorfier)) @@ -229,13 +185,19 @@ getcolors(colorfier::Colorfier{<:Values{Colorant}}) = values(colorfier) function getcolors(colorfier::Colorfier{<:Values{DateTime}}) dvalues = datetime2unix.(values(colorfier)) - dcolorfier = update(colorfier, values=dvalues) + dalphas = alphas(colorfier) + dcolorscheme = colorscheme(colorfier) + dcolorrange = colorrange(colorfier) + dcolorfier = Colorfier(dvalues; alphas=dalphas, colorscheme=dcolorscheme, colorrange=dcolorrange) getcolors(dcolorfier) end function getcolors(colorfier::Colorfier{<:Values{Date}}) dvalues = map(d -> datetime2unix(DateTime(d)), values(colorfier)) - dcolorfier = update(colorfier, values=dvalues) + dalphas = alphas(colorfier) + dcolorscheme = colorscheme(colorfier) + dcolorrange = colorrange(colorfier) + dcolorfier = Colorfier(dvalues; alphas=dalphas, colorscheme=dcolorscheme, colorrange=dcolorrange) getcolors(dcolorfier) end diff --git a/test/runtests.jl b/test/runtests.jl index cbe327e..6ca8347 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -207,20 +207,14 @@ using Test stds = rand(10) values = Normal.(means, stds) alphas = 1 .- (stds .- minimum(stds)) ./ (maximum(stds) - minimum(stds)) - colors = colorfy(means) - @test colorfy(values) == coloralpha.(colors, alphas) - @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) - - values = Normal.(means, fill(0.5, 10)) - @test colorfy(values) == coloralpha.(colors, 1) + colors = colorfy(means; alphas) + @test colorfy(values) == colors @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) values = [missing, Normal(0.5, 0.5), Normal(0.6, 0.6), Normal(0.7, 0.7), missing] - colors = [colorant"transparent"; colorfy([0.5, 0.6, 0.7]); colorant"transparent"] - alphas = [0.0, 1.0, 0.5, 0.0, 0.0] - colors = colorfy(values) - @test colorfy(values) == coloralpha.(colors, alphas) - @test colorfy(values, alphas=0.5) == coloralpha.(colors, [0.0, 0.5, 0.5, 0.5, 0.0]) + colors = [colorant"transparent"; colorfy([0.5, 0.6, 0.7], alphas=[1.0, 0.5, 0.0]); colorant"transparent"] + @test colorfy(values) == colors + @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) end @testset "Unitful" begin From a4a55351ae90613d16cc0dbd90f106210da491d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 17:51:24 -0300 Subject: [PATCH 02/16] More refactoring --- ext/ColorfyCategoricalArraysExt.jl | 17 +-- ext/ColorfyDistributionsExt.jl | 31 ++--- ext/ColorfyUnitfulExt.jl | 17 +-- src/Colorfy.jl | 201 +++++++---------------------- 4 files changed, 72 insertions(+), 194 deletions(-) diff --git a/ext/ColorfyCategoricalArraysExt.jl b/ext/ColorfyCategoricalArraysExt.jl index dcc2f99..f1bd9d6 100644 --- a/ext/ColorfyCategoricalArraysExt.jl +++ b/ext/ColorfyCategoricalArraysExt.jl @@ -4,18 +4,15 @@ module ColorfyCategoricalArraysExt -using Colorfy -using Colorfy: Values -using ColorSchemes: colorschemes -using CategoricalArrays: CategoricalValue, levels, levelcode +using CategoricalArrays: CategoricalValue +using CategoricalArrays: levels, levelcode -function Colorfy.getcolors(colorfier::Colorfier{<:Values{CategoricalValue}}) - values = Colorfy.values(colorfier) - colorscheme = Colorfy.colorscheme(colorfier) +import Colorfy + +function Colorfy.repr(values::AbstractVector{<:CategoricalValue}, colorscheme, colorrange) nlevels = length(levels(values)) - categcolors = colorscheme[range(0, nlevels > 1 ? 1 : 0, length=nlevels)] - colors = categcolors[levelcode.(values)] - coloralpha.(colors, alphas(colorfier)) + lcolors = colorscheme[range(0, nlevels > 1 ? 1 : 0, length=nlevels)] + lcolors[levelcode.(values)] end end diff --git a/ext/ColorfyDistributionsExt.jl b/ext/ColorfyDistributionsExt.jl index 9ae3705..5c39f21 100644 --- a/ext/ColorfyDistributionsExt.jl +++ b/ext/ColorfyDistributionsExt.jl @@ -4,28 +4,25 @@ module ColorfyDistributionsExt -using Colorfy -using Colorfy: Values -using Distributions: Distribution, location, scale +using Distributions: Distribution +using Distributions: location, scale -function Colorfy.getcolors(colorfier::Colorfier{<:Values{Distribution}}) +import Colorfy + +function Colorfy.repr(values::AbstractVector{<:Distribution}, colorscheme, colorrange) # extract location and scale parameters - v = Colorfy.values(colorfier) - m = location.(v) - s = scale.(v) - a, b = extrema(s) + μs = location.(values) + σs = scale.(values) + a, b = extrema(σs) - # build new colorfier with location as values and alphas based on scale - values = m - alphas = if a == b - fill(1, length(v)) + # compute alphas based on scale parameters + αs = if a == b + fill(1, length(μs)) else - @. 1 - (s - a) / (b - a) + @. 1 - (σs - a) / (b - a) end - colorscheme = Colorfy.colorscheme(colorfier) - colorrange = Colorfy.colorrange(colorfier) - colorfier′ = Colorfier(values; alphas, colorscheme, colorrange) - Colorfy.getcolors(colorfier′) + + Colorfy.repr(μs, αs, colorscheme, colorrange) end end diff --git a/ext/ColorfyUnitfulExt.jl b/ext/ColorfyUnitfulExt.jl index 785ed08..10e9f4c 100644 --- a/ext/ColorfyUnitfulExt.jl +++ b/ext/ColorfyUnitfulExt.jl @@ -4,18 +4,13 @@ module ColorfyUnitfulExt -using Colorfy -using Colorfy: Values -using Unitful: Quantity, ustrip +using Unitful: Quantity +using Unitful: ustrip -function Colorfy.getcolors(colorfier::Colorfier{<:Values{Quantity}}) - values = ustrip.(Colorfy.values(colorfier)) - alphas = Colorfy.alphas(colorfier) - colorscheme = Colorfy.colorscheme(colorfier) - colorrange = Colorfy.colorrange(colorfier) - colorfier′ = Colorfier(values; alphas, colorscheme, colorrange) - Colorfy.getcolors(colorfier′) -end +import Colorfy + +Colorfy.repr(values::AbstractVector{<:Quantity}, colorscheme, colorrange) = + Colorfy.repr(ustrip.(values), colorscheme, colorrange) Colorfy.ascolorrange(colorrange::NTuple{2,Quantity}) = Colorfy.ascolorrange(ustrip.(colorrange)) diff --git a/src/Colorfy.jl b/src/Colorfy.jl index 37fb096..19bac2a 100644 --- a/src/Colorfy.jl +++ b/src/Colorfy.jl @@ -9,100 +9,52 @@ using ColorSchemes using FixedPointNumbers using Dates -export Colorfier, colorfy - -# type alias to reduce typing -const Values{T} = AbstractVector{<:T} +export colorfy """ - Colorfier(values; [alphas, colorscheme, colorrange]) + colorfy(values; alpha=1.0, colorscheme=:viridis, colorrange=:extrema) -Maps each value in `values` to a color. Colors can be obtained using the [`Colorfy.colors`](@ref) function. +Convert `values` to Colors.jl colors based on given options. ## Options -* `alphas` - Scalar or a vector of color alphas (default to `1.0`); -* `colorscheme` - Color scheme specification (default to `:viridis`); -* `colorrange` - Tuple with minimum and maximum color values or a symbol that can be passed - to the `rangescale` argument of the `ColorSchemes.get` function (default to `:extrema`); +* `alpha` - Scalar or vector of transparency values +* `colorscheme` - Color scheme from ColorSchemes.jl +* `colorrange` - Minimum and maximum values or a symbol """ -struct Colorfier{V,A,S,R} - values::V - alphas::A - colorscheme::S - colorrange::R -end +function colorfy(values; alpha=1.0, colorscheme=:viridis, colorrange=:extrema) + # handle input arguments + vs, αs, s, r = handleargs(values, alpha, colorscheme, colorrange) -function Colorfier(values; alphas=1.0, colorscheme=:viridis, colorrange=:extrema) - values′ = asvalues(values) - alphas′ = asalphas(alphas, values′) - colorscheme′ = ascolorscheme(colorscheme) - colorrange′ = ascolorrange(colorrange) - Colorfier(values′, alphas′, colorscheme′, colorrange′) + # find invalid and valid indices + iinds = findall(isinvalid, vs) + vinds = setdiff(1:length(vs), iinds) + + # construct colors for valid values + rcolors = repr(nonmissingvec(vs[vinds]), s, r) + vcolors = coloralpha.(rcolors, αs[vinds]) + if isempty(iinds) # all values are valid, return colors directly + vcolors + else # set "transparent" color for invalid values + genvec(vinds, vcolors, iinds, colorant"transparent", length(values)) + end end -""" - colorfy(values; kwargs...) - -Shortcut to `Colorfy.colors(Colorfier(values; kwargs...))` for convenience. - -See also [`Colorfier`](@ref), [`Colorfy.colors`](@ref). -""" -colorfy(values; kwargs...) = colors(Colorfier(values; kwargs...)) - -# -------- -# GETTERS -# -------- - -""" - Colorfy.values(colorfier) - -Values of the `colorfier`. -""" -values(colorfier::Colorfier) = colorfier.values - -""" - Colorfy.alphas(colorfier) - -Color alphas of the `colorfier`. -""" -alphas(colorfier::Colorfier) = colorfier.alphas - -""" - Colorfy.colorscheme(colorfier) - -Color scheme of the `colorfier`. -""" -colorscheme(colorfier::Colorfier) = colorfier.colorscheme - -""" - Colorfy.colorrange(colorfier) - -Color range of the `colorfier`. -""" -colorrange(colorfier::Colorfier) = colorfier.colorrange - -# ---- -# API -# ---- - -""" - Colorfy.asvalues(values) +function handleargs(values, alphas, colorscheme, colorrange) + vs = asvalues(values) + αs = asalphas(alphas, vs) + s = ascolorscheme(colorscheme) + r = ascolorrange(colorrange) + vs, αs, s, r +end -Valid color values for a given `values`. -""" asvalues(values) = values -asvalues(values::Values{Colorant}) = values -asvalues(values::Values{Colorant{Q0f7}}) = fixcolors(values) -asvalues(values::Values{Colorant{Q0f15}}) = fixcolors(values) -asvalues(values::Values{Colorant{Q0f31}}) = fixcolors(values) -asvalues(values::Values{Colorant{Q0f63}}) = fixcolors(values) +asvalues(values::AbstractVector{<:Colorant}) = values +asvalues(values::AbstractVector{<:Colorant{Q0f7}}) = fixcolors(values) +asvalues(values::AbstractVector{<:Colorant{Q0f15}}) = fixcolors(values) +asvalues(values::AbstractVector{<:Colorant{Q0f31}}) = fixcolors(values) +asvalues(values::AbstractVector{<:Colorant{Q0f63}}) = fixcolors(values) -""" - Colorfy.asalphas(alphas, values) - -Valid color alphas for a given `alphas` and `values`. -""" asalphas(alpha::Number, values) = fill(alpha, length(values)) function asalphas(alphas::AbstractVector, values) if length(alphas) ≠ length(values) @@ -111,103 +63,40 @@ function asalphas(alphas::AbstractVector, values) alphas end -""" - Colorfy.ascolorscheme(colorscheme) - -Valid `ColorScheme` object for a given `colorscheme` specification. -""" ascolorscheme(colorscheme::Symbol) = colorschemes[colorscheme] ascolorscheme(colorscheme::AbstractString) = ascolorscheme(Symbol(colorscheme)) ascolorscheme(colorscheme::AbstractVector) = ColorScheme([parse(Colorant, color) for color in colorscheme]) ascolorscheme(colorscheme::ColorScheme) = colorscheme -""" - Colorfy.ascolorrange(colorrange) - -Valid color range, accepted by the `ColorSchemes.get` function, for a given `colorrange`. -""" ascolorrange(colorrange::Symbol) = colorrange ascolorrange(colorrange::NTuple{2,T}) where {T<:Real} = colorrange ascolorrange(colorrange::NTuple{2,Real}) = promote(colorrange...) -""" - Colorfy.colors(colorfier) +# ---------------- +# IMPLEMENTATIONS +# ---------------- -Colors mapped from the `colorfier` . -""" -function colors(colorfier::Colorfier) - # find invalid and valid indices - isinvalid(v) = ismissing(v) || (v isa Number && !isfinite(v)) - vals = values(colorfier) - iinds = findall(isinvalid, vals) - vinds = setdiff(1:length(vals), iinds) - - # construct new colorfier with valid values only - vvalues = nonmissingvec(vals[vinds]) - valphas = alphas(colorfier)[vinds] - vcolorscheme = colorscheme(colorfier) - vcolorrange = colorrange(colorfier) - vcolorfier = Colorfier(vvalues; alphas=valphas, colorscheme=vcolorscheme, colorrange=vcolorrange) - - if isempty(iinds) - # all values are valid, so we can directly dispatch methods - getcolors(vcolorfier) - else - # get valid colors and set "transparent" for invalid values - vcolors = colors(vcolorfier) - icolor = colorant"transparent" - genvec(vinds, vcolors, iinds, icolor, length(vals)) - end -end +repr(values::AbstractVector{<:Colorant}, colorscheme, colorrange) = values -""" - Colorfy.getcolors(colorfier, values) +repr(values::AbstractVector{<:Number}, colorscheme, colorrange) = get(colorscheme, values, colorrange) -Function intended for developers that returns the mapped colors from the `colorfier`. -""" -function getcolors(colorfier::Colorfier) - throw(ArgumentError(""" - Values of type `$(eltype(colorfier.values))` are not supported. - Please make sure your vector of colors has a concrete type. - """)) -end +repr(values::AbstractVector{<:Symbol}, colorscheme, colorrange) = repr(string.(values), colorscheme, colorrange) -function getcolors(colorfier::Colorfier{<:Values{Number}}) - colors = get(colorscheme(colorfier), values(colorfier), colorrange(colorfier)) - coloralpha.(colors, alphas(colorfier)) -end +repr(values::AbstractVector{<:AbstractString}, colorscheme, colorrange) = parse.(Ref(Colorant), values) -getcolors(colorfier::Colorfier{<:Values{AbstractString}}) = parse.(Ref(Colorant), values(colorfier)) +repr(values::AbstractVector{<:Date}, colorscheme, colorrange) = repr(DateTime.(values), colorscheme, colorrange) -getcolors(colorfier::Colorfier{<:Values{Symbol}}) = parse.(Ref(Colorant), string.(values(colorfier))) - -getcolors(colorfier::Colorfier{<:Values{Colorant}}) = values(colorfier) - -function getcolors(colorfier::Colorfier{<:Values{DateTime}}) - dvalues = datetime2unix.(values(colorfier)) - dalphas = alphas(colorfier) - dcolorscheme = colorscheme(colorfier) - dcolorrange = colorrange(colorfier) - dcolorfier = Colorfier(dvalues; alphas=dalphas, colorscheme=dcolorscheme, colorrange=dcolorrange) - getcolors(dcolorfier) -end - -function getcolors(colorfier::Colorfier{<:Values{Date}}) - dvalues = map(d -> datetime2unix(DateTime(d)), values(colorfier)) - dalphas = alphas(colorfier) - dcolorscheme = colorscheme(colorfier) - dcolorrange = colorrange(colorfier) - dcolorfier = Colorfier(dvalues; alphas=dalphas, colorscheme=dcolorscheme, colorrange=dcolorrange) - getcolors(dcolorfier) -end +repr(values::AbstractVector{<:DateTime}, colorscheme, colorrange) = repr(datetime2unix.(values), colorscheme, colorrange) # ----------------- # HELPER FUNCTIONS # ----------------- +isinvalid(value) = ismissing(value) || (value isa Number && !isfinite(value)) + fixcolors(colors) = convert.(floattype(eltype(colors)), colors) -nonmissingvec(x::AbstractVector{T}) where {T} = convert(AbstractVector{nonmissingtype(T)}, x) +nonmissingvec(values::AbstractVector{T}) where {T} = convert(AbstractVector{nonmissingtype(T)}, values) function genvec(vecinds, vec, valinds, val, len) valdict = Dict(i => val for i in valinds) From 23ec5df8c7e36386afe03b548d895a6d304182ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 20:11:39 -0300 Subject: [PATCH 03/16] Refactor tests --- ext/ColorfyDistributionsExt.jl | 2 +- test/runtests.jl | 274 ++++++++++++++------------------- 2 files changed, 115 insertions(+), 161 deletions(-) diff --git a/ext/ColorfyDistributionsExt.jl b/ext/ColorfyDistributionsExt.jl index 5c39f21..81beadc 100644 --- a/ext/ColorfyDistributionsExt.jl +++ b/ext/ColorfyDistributionsExt.jl @@ -22,7 +22,7 @@ function Colorfy.repr(values::AbstractVector{<:Distribution}, colorscheme, color @. 1 - (σs - a) / (b - a) end - Colorfy.repr(μs, αs, colorscheme, colorrange) + Colorfy.colorfy(μs; alpha=αs, colorscheme, colorrange) end end diff --git a/test/runtests.jl b/test/runtests.jl index 6ca8347..6692ceb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,157 +9,103 @@ using Dates using Test @testset "Colorfy.jl" begin - @testset "Colorfier" begin + @testset "Handle arguments" begin values = rand(10) - colorfier = Colorfier(values) - @test Colorfy.values(colorfier) == values - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:viridis] - @test Colorfy.colorrange(colorfier) == :extrema - - colors = [Gray(rand()) for _ in 1:10] - colorfier = Colorfier(colors) - @test eltype(Colorfy.values(colorfier)) <: Gray - @test eltype(eltype(Colorfy.values(colorfier))) <: AbstractFloat - @test Colorfy.values(colorfier) == colors - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:viridis] - @test Colorfy.colorrange(colorfier) == :extrema - - colors = [Gray(rand(Q0f7)) for _ in 1:10] - colorfier = Colorfier(colors) - @test eltype(Colorfy.values(colorfier)) <: Gray - @test eltype(eltype(Colorfy.values(colorfier))) <: AbstractFloat - @test Colorfy.values(colorfier) == colors - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:viridis] - @test Colorfy.colorrange(colorfier) == :extrema - - colors = [Gray(rand(Q0f15)) for _ in 1:10] - colorfier = Colorfier(colors) - @test eltype(Colorfy.values(colorfier)) <: Gray - @test eltype(eltype(Colorfy.values(colorfier))) <: AbstractFloat - @test Colorfy.values(colorfier) == colors - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:viridis] - @test Colorfy.colorrange(colorfier) == :extrema - - colors = [Gray(rand(Q0f31)) for _ in 1:10] - colorfier = Colorfier(colors) - @test eltype(Colorfy.values(colorfier)) <: Gray - @test eltype(eltype(Colorfy.values(colorfier))) <: AbstractFloat - @test Colorfy.values(colorfier) == colors - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:viridis] - @test Colorfy.colorrange(colorfier) == :extrema - - colors = [Gray(rand(Q0f63)) for _ in 1:10] - colorfier = Colorfier(colors) - @test eltype(Colorfy.values(colorfier)) <: Gray - @test eltype(eltype(Colorfy.values(colorfier))) <: AbstractFloat - @test Colorfy.values(colorfier) == colors - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:viridis] - @test Colorfy.colorrange(colorfier) == :extrema - - colorfier = Colorfier(values, alphas=0.5) - @test Colorfy.values(colorfier) == values - @test Colorfy.alphas(colorfier) == fill(0.5, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:viridis] - @test Colorfy.colorrange(colorfier) == :extrema - - colorfier = Colorfier(values, colorscheme=:grays) - @test Colorfy.values(colorfier) == values - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:grays] - @test Colorfy.colorrange(colorfier) == :extrema - - colorfier = Colorfier(values, colorscheme="grays") - @test Colorfy.values(colorfier) == values - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:grays] - @test Colorfy.colorrange(colorfier) == :extrema - - colorfier = Colorfier(values, colorscheme=["black", "white"]) - @test Colorfy.values(colorfier) == values - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier)[0.0] == colorant"black" - @test Colorfy.colorscheme(colorfier)[1.0] == colorant"white" - @test Colorfy.colorrange(colorfier) == :extrema - - colorfier = Colorfier(values, colorrange=(0.25, 0.75)) - @test Colorfy.values(colorfier) == values - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:viridis] - @test Colorfy.colorrange(colorfier) == (0.25, 0.75) - - colorfier = Colorfier(values, colorrange=(0, 0.5)) - @test Colorfy.values(colorfier) == values - @test Colorfy.alphas(colorfier) == fill(1, 10) - @test Colorfy.colorscheme(colorfier) == colorschemes[:viridis] - @test Colorfy.colorrange(colorfier) == (0.0, 0.5) + vs, αs, s, r = Colorfy.handleargs(values, 1.0, :viridis, :extrema) + @test vs == values + @test αs == fill(1, 10) + @test s == colorschemes[:viridis] + @test r == :extrema + + values = [Gray(rand()) for _ in 1:10] + vs, αs, s, r = Colorfy.handleargs(values, 1.0, :viridis, :extrema) + @test eltype(vs) <: Gray + @test eltype(eltype(vs)) <: AbstractFloat + @test vs == values + @test αs == fill(1, 10) + @test s == colorschemes[:viridis] + @test r == :extrema + + for Q in (Q0f7, Q0f15, Q0f31, Q0f63) + values = [Gray(rand(Q)) for _ in 1:10] + vs, αs, s, r = Colorfy.handleargs(values, 1.0, :viridis, :extrema) + @test eltype(vs) <: Gray + @test eltype(eltype(vs)) <: AbstractFloat + @test vs == values + @test αs == fill(1, 10) + @test s == colorschemes[:viridis] + @test r == :extrema + end + values = rand(10) + vs, αs, s, r = Colorfy.handleargs(values, 0.5, :viridis, :extrema) + @test αs == fill(0.5, 10) + + values = rand(10) + vs, αs, s, r = Colorfy.handleargs(values, 1.0, :grays, :extrema) + @test s == colorschemes[:grays] + + values = rand(10) + vs, αs, s, r = Colorfy.handleargs(values, 1.0, "grays", :extrema) + @test s == colorschemes[:grays] + + values = rand(10) + vs, αs, s, r = Colorfy.handleargs(values, 1.0, ["black", "white"], :extrema) + @test s[0.0] == colorant"black" + @test s[1.0] == colorant"white" + + values = rand(10) + vs, αs, s, r = Colorfy.handleargs(values, 1.0, :viridis, (0.25, 0.75)) + @test r == (0.25, 0.75) + + values = rand(10) + vs, αs, s, r = Colorfy.handleargs(values, 1.0, :viridis, (0, 0.5)) + @test r == (0.0, 0.5) + + values = rand(10) alphas = rand(10) - colorfier = Colorfier(values; alphas, colorscheme=colorschemes[:grays], colorrange=(0.25, 0.75)) - @test Colorfy.values(colorfier) == values - @test Colorfy.alphas(colorfier) == alphas - @test Colorfy.colorscheme(colorfier) == colorschemes[:grays] - @test Colorfy.colorrange(colorfier) == (0.25, 0.75) + vs, αs, s, r = Colorfy.handleargs(values, alphas, colorschemes[:grays], (0.25, 0.75)) + @test vs == values + @test αs == alphas + @test s == colorschemes[:grays] + @test r == (0.25, 0.75) # error: the number of alphas must be equal to the number of values + values = rand(10) alphas = rand(9) - @test_throws ArgumentError Colorfier(values; alphas) + @test_throws ArgumentError Colorfy.handleargs(values, alphas, :viridis, :extrema) end - @testset "Colorfy.colors" begin + @testset "Basic" begin values = rand(10) - colorfier = Colorfier(values) - colors = get(colorschemes[:viridis], values, :extrema) - @test Colorfy.colors(colorfier) == coloralpha.(colors, 1) + colors = colorfy(values) + result = get(colorschemes[:viridis], values, :extrema) + @test colors == coloralpha.(result, 1) - colorfier = Colorfier(values, colorscheme=:grays, colorrange=(0.25, 0.75)) - colors = get(colorschemes[:grays], values, (0.25, 0.75)) - @test Colorfy.colors(colorfier) == coloralpha.(colors, 1) + values = rand(10) + colors = colorfy(values, colorscheme=:grays, colorrange=(0.25, 0.75)) + result = get(colorschemes[:grays], values, (0.25, 0.75)) + @test colors == coloralpha.(result, 1) - colors = [colorant"red", colorant"green", colorant"blue", colorant"white", colorant"black"] values = ["red", "green", "blue", "white", "black"] - colorfier = Colorfier(values) - @test Colorfy.colors(colorfier) == coloralpha.(colors, 1) + colors = colorfy(values) + result = [colorant"red", colorant"green", colorant"blue", colorant"white", colorant"black"] + @test colors == coloralpha.(result, 1) values = [:red, :green, :blue, :white, :black] - colorfier = Colorfier(values, alphas=0.5) - @test Colorfy.colors(colorfier) == coloralpha.(colors, 0.5) + colors1 = colorfy(values) + colors2 = colorfy(values, alpha=0.5) + @test colors2 == coloralpha.(colors1, 0.5) - values = colors + values = [:red, :green, :blue, :white, :black] alphas = rand(5) - colorfier = Colorfier(values; alphas) - @test Colorfy.colors(colorfier) == coloralpha.(colors, alphas) + colors1 = colorfy(values) + colors2 = colorfy(values, alpha=alphas) + @test colors2 == coloralpha.(colors1, alphas) - values = coloralpha.(colors, alphas) - colorfier = Colorfier(values) - @test Colorfy.colors(colorfier) == values - - # error: unsupported values - values = [nothing, nothing, nothing] - colorfier = Colorfier(values) - @test_throws ArgumentError Colorfy.colors(colorfier) - values = Any[:red, :green, :blue] # vector with non-concrete eltype - colorfier = Colorfier(values) - @test_throws ArgumentError Colorfy.colors(colorfier) - end - - @testset "colorfy" begin - values = rand(10) - colors = get(colorschemes[:viridis], values, :extrema) - @test colorfy(values) == coloralpha.(colors, 1) - - colors = get(colorschemes[:grays], values, (0.25, 0.75)) - @test colorfy(values, colorscheme=:grays, colorrange=(0.25, 0.75)) == coloralpha.(colors, 1) - end - - @testset "Invalid values" begin + # invalid values (missing, NaN, Inf) should be colored as "transparent" values = [0.1, missing, 0.2, NaN, 0.3, Inf, 0.4, -Inf, 0.5] - colors = colorfy(values, alphas=0.5) + colors = colorfy(values, alpha=0.5) @test colors[2] == colorant"transparent" @test colors[4] == colorant"transparent" @test colors[6] == colorant"transparent" @@ -168,59 +114,67 @@ using Test # Vector{Union{Missing,T}} whitout missing values values = Union{Missing,Int}[1, 2, 3, 4, 5] @test colorfy(values) == colorfy([1, 2, 3, 4, 5]) + + # error: unsupported values + values = [nothing, nothing, nothing] + @test_throws MethodError colorfy(values) + values = Any[:red, :green, :blue] # vector with non-concrete eltype + @test_throws MethodError colorfy(values) end @testset "Dates" begin values = now() .+ Day.(1:10) - colors = colorfy(datetime2unix.(values)) - @test colorfy(values) == colors - @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) + colors1 = colorfy(values) + colors2 = colorfy(datetime2unix.(values)) + @test colors1 == colors2 + @test colorfy(values, alpha=0.5) == coloralpha.(colors1, 0.5) values = today() .+ Day.(1:10) - colors = colorfy(datetime2unix.(DateTime.(values))) - @test colorfy(values) == colors - @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) + colors1 = colorfy(values) + colors2 = colorfy(DateTime.(values)) + @test colors1 == colors2 + @test colorfy(values, alpha=0.5) == coloralpha.(colors1, 0.5) end @testset "CategoricalArrays" begin values = categorical(["n", "n", "y", "y", "n", "y"], levels=["y", "n"]) - categcolors = colorschemes[:viridis][range(0, 1, length=2)] - colors = categcolors[[2, 2, 1, 1, 2, 1]] - @test colorfy(values) == coloralpha.(colors, 1) - @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) + lcolors = colorschemes[:viridis][range(0, 1, length=2)] + result = lcolors[[2, 2, 1, 1, 2, 1]] + @test colorfy(values) == coloralpha.(result, 1) + @test colorfy(values, alpha=0.5) == coloralpha.(result, 0.5) values = categorical([2, 1, 1, 3, 1, 3, 3, 2, 1, 2], levels=1:3) - categcolors = colorschemes[:viridis][range(0, 1, length=3)] - colors = categcolors[[2, 1, 1, 3, 1, 3, 3, 2, 1, 2]] - @test colorfy(values) == coloralpha.(colors, 1) - @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) + lcolors = colorschemes[:viridis][range(0, 1, length=3)] + result = lcolors[[2, 1, 1, 3, 1, 3, 3, 2, 1, 2]] + @test colorfy(values) == coloralpha.(result, 1) + @test colorfy(values, alpha=0.5) == coloralpha.(result, 0.5) values = categorical([1, 1, 1, 1, 1], levels=[1]) - categcolors = colorschemes[:viridis][range(0, 0, length=1)] - colors = categcolors[[1, 1, 1, 1, 1]] - @test colorfy(values) == coloralpha.(colors, 1) - @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) + lcolors = colorschemes[:viridis][range(0, 0, length=1)] + result = lcolors[[1, 1, 1, 1, 1]] + @test colorfy(values) == coloralpha.(result, 1) + @test colorfy(values, alpha=0.5) == coloralpha.(result, 0.5) end @testset "Distributions" begin - means = rand(10) - stds = rand(10) - values = Normal.(means, stds) - alphas = 1 .- (stds .- minimum(stds)) ./ (maximum(stds) - minimum(stds)) - colors = colorfy(means; alphas) + values = Normal.(rand(10), rand(10)) + μs = location.(values) + σs = scale.(values) + a, b = extrema(σs) + colors = colorfy(μs, alpha=1 .- (σs .- a) ./ (b .- a)) @test colorfy(values) == colors - @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) + @test colorfy(values, alpha=0.5) == coloralpha.(colors, 0.5) values = [missing, Normal(0.5, 0.5), Normal(0.6, 0.6), Normal(0.7, 0.7), missing] - colors = [colorant"transparent"; colorfy([0.5, 0.6, 0.7], alphas=[1.0, 0.5, 0.0]); colorant"transparent"] + colors = [colorant"transparent"; colorfy([0.5, 0.6, 0.7], alpha=[1.0, 0.5, 0.0]); colorant"transparent"] @test colorfy(values) == colors - @test colorfy(values, alphas=0.5) == coloralpha.(colors, 0.5) + @test colorfy(values, alpha=0.5) == coloralpha.(colors, 0.5) end @testset "Unitful" begin values = rand(10) * u"m" @test colorfy(values) == colorfy(ustrip.(values)) - @test colorfy(values, alphas=0.5) == colorfy(ustrip.(values), alphas=0.5) + @test colorfy(values, alpha=0.5) == colorfy(ustrip.(values), alpha=0.5) @test colorfy(values, colorrange=(0.25, 0.75)) == colorfy(ustrip.(values), colorrange=(0.25, 0.75)) @test colorfy(values, colorrange=(0.25u"m", 0.75u"m")) == colorfy(ustrip.(values), colorrange=(0.25, 0.75)) end From 30c270db4cbb5839658f628d71725141350acb1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 20:21:14 -0300 Subject: [PATCH 04/16] Move test deps to main Project.toml --- Project.toml | 12 ++++++++++++ test/Project.toml | 9 --------- 2 files changed, 12 insertions(+), 9 deletions(-) delete mode 100644 test/Project.toml diff --git a/Project.toml b/Project.toml index 0a9b901..65df7a5 100644 --- a/Project.toml +++ b/Project.toml @@ -28,3 +28,15 @@ Distributions = "0.25" FixedPointNumbers = "0.8" Unitful = "1.19" julia = "1.10" + +[extras] +CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597" +ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4" +Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" + +[targets] +test = ["Test", "CategoricalArrays", "ColorSchemes", "Colors", "Distributions", "FixedPointNumbers", "Unitful"] diff --git a/test/Project.toml b/test/Project.toml deleted file mode 100644 index 44e5f85..0000000 --- a/test/Project.toml +++ /dev/null @@ -1,9 +0,0 @@ -[deps] -CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597" -ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4" -Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" -Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" -Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" -FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" From df8d52e328b3fca1cc6f196ef4d57d227dbf03c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 20:29:48 -0300 Subject: [PATCH 05/16] Improve docs --- README.md | 48 +++++++++--------------------------------------- src/Colorfy.jl | 7 +++++++ 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index decc909..011291d 100644 --- a/README.md +++ b/README.md @@ -3,55 +3,25 @@ [![Build Status](https://github.com/JuliaGraphics/Colorfy.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/JuliaGraphics/Colorfy.jl/actions/workflows/CI.yml?query=branch%3Amain) [![Coverage](https://codecov.io/gh/JuliaGraphics/Colorfy.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/JuliaGraphics/Colorfy.jl) -Colorfy.jl is a package for mapping Julia objects into colors defined by [Colors.jl](https://github.com/JuliaGraphics/Colors.jl). +Colorfy.jl is a package for mapping Julia objects into colors +defined by [Colors.jl](https://github.com/JuliaGraphics/Colors.jl). ## Usage -The use of this package is centralized in the `Colorfier` struct. -This type stores the necessary info to convert a list of values into a list of colors. -To extract the mapped colors from the colorfier, use the `Colorfy.colors` function: - -```julia -julia> values = rand(5) -5-element Vector{Float64}: - 0.6725922579880301 - 0.48419175677473947 - 0.555196651363384 - 0.9391048282597594 - 0.06139286380440967 - -julia> colorfier = Colorfier(values); - -julia> Colorfy.colors(colorfier) -5-element Array{RGBA{Float64},1} with eltype ColorTypes.RGBA{Float64}: - RGBA{Float64}(0.25686927730139364,0.744083158641258,0.4461059964847054,1.0) - RGBA{Float64}(0.13396134240010413,0.5479273380822499,0.5536215374839386,1.0) - RGBA{Float64}(0.12033938235581829,0.6238620558326493,0.534269659829833,1.0) - RGBA{Float64}(0.993248,0.906157,0.143936,1.0) - RGBA{Float64}(0.267004,0.004874,0.329415,1.0) - -julia> colorfier = Colorfier(values, alphas=0.5); - -julia> Colorfy.colors(colorfier) -5-element Array{RGBA{Float64},1} with eltype ColorTypes.RGBA{Float64}: - RGBA{Float64}(0.25686927730139364,0.744083158641258,0.4461059964847054,0.5) - RGBA{Float64}(0.13396134240010413,0.5479273380822499,0.5536215374839386,0.5) - RGBA{Float64}(0.12033938235581829,0.6238620558326493,0.534269659829833,0.5) - RGBA{Float64}(0.993248,0.906157,0.143936,0.5) - RGBA{Float64}(0.267004,0.004874,0.329415,0.5) -``` - -For convenience, the `colorfy` function is defined. This function -creates a `Colorfier` instance and calls the `Colorfy.colors` function: +The `colorfy` function takes any vector of values with options +(e.g., alpha, colorscheme) and converts into valid colors: ```julia julia> values = [:red, :green, :blue]; -julia> colorfy(values, alphas=[0.5, 0.6, 0.7]) +julia> colorfy(values, alpha=[0.5, 0.6, 0.7]) 3-element Array{RGBA{N0f8},1} with eltype ColorTypes.RGBA{FixedPointNumbers.N0f8}: RGBA{N0f8}(1.0,0.0,0.0,0.5) RGBA{N0f8}(0.0,0.502,0.0,0.6) RGBA{N0f8}(0.0,0.0,1.0,0.7) ``` -See the `Colorfier` docstring for a description of all options. +Please check the `colorfy` docstring for more details. + +Developers can register colorful representations for their +types by implementing methods for the `Colorfy.repr` function. diff --git a/src/Colorfy.jl b/src/Colorfy.jl index 19bac2a..1ab1d3c 100644 --- a/src/Colorfy.jl +++ b/src/Colorfy.jl @@ -76,6 +76,13 @@ ascolorrange(colorrange::NTuple{2,Real}) = promote(colorrange...) # IMPLEMENTATIONS # ---------------- +""" + repr(values::AbstractVector{T}, colorscheme, colorrange) + +Colorful representation of `values` of type `T` based on `colorscheme` and `colorrange`. +""" +function repr end + repr(values::AbstractVector{<:Colorant}, colorscheme, colorrange) = values repr(values::AbstractVector{<:Number}, colorscheme, colorrange) = get(colorscheme, values, colorrange) From 2003a96bba2c292cd055ea4e246e67a781b1ca8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 20:32:21 -0300 Subject: [PATCH 06/16] Refactor tests --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 6692ceb..ec37252 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -76,7 +76,7 @@ using Test @test_throws ArgumentError Colorfy.handleargs(values, alphas, :viridis, :extrema) end - @testset "Basic" begin + @testset "Basic tests" begin values = rand(10) colors = colorfy(values) result = get(colorschemes[:viridis], values, :extrema) From 8fc6c1d761dbebb161762a6ff97c0847dece5ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 20:44:44 -0300 Subject: [PATCH 07/16] Refactor Distributions extension --- ext/ColorfyDistributionsExt.jl | 7 ++++++- test/runtests.jl | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ext/ColorfyDistributionsExt.jl b/ext/ColorfyDistributionsExt.jl index 81beadc..d4e4fd0 100644 --- a/ext/ColorfyDistributionsExt.jl +++ b/ext/ColorfyDistributionsExt.jl @@ -6,6 +6,7 @@ module ColorfyDistributionsExt using Distributions: Distribution using Distributions: location, scale +using Colors: coloralpha import Colorfy @@ -22,7 +23,11 @@ function Colorfy.repr(values::AbstractVector{<:Distribution}, colorscheme, color @. 1 - (σs - a) / (b - a) end - Colorfy.colorfy(μs; alpha=αs, colorscheme, colorrange) + # get colors for location parameters + cs = Colorfy.repr(μs, colorscheme, colorrange) + + # apply alphas to colors + coloralpha.(cs, αs) end end diff --git a/test/runtests.jl b/test/runtests.jl index ec37252..d437030 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -162,6 +162,7 @@ using Test σs = scale.(values) a, b = extrema(σs) colors = colorfy(μs, alpha=1 .- (σs .- a) ./ (b .- a)) + colorfy(values) @test colorfy(values) == colors @test colorfy(values, alpha=0.5) == coloralpha.(colors, 0.5) From 0d3a77a631b026ec595f814cdd1c38907c46302c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 20:50:22 -0300 Subject: [PATCH 08/16] More refactoring --- ext/ColorfyDistributionsExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ColorfyDistributionsExt.jl b/ext/ColorfyDistributionsExt.jl index d4e4fd0..c7a8f1e 100644 --- a/ext/ColorfyDistributionsExt.jl +++ b/ext/ColorfyDistributionsExt.jl @@ -14,9 +14,9 @@ function Colorfy.repr(values::AbstractVector{<:Distribution}, colorscheme, color # extract location and scale parameters μs = location.(values) σs = scale.(values) - a, b = extrema(σs) # compute alphas based on scale parameters + a, b = extrema(σs) αs = if a == b fill(1, length(μs)) else From 3aed803dcd21bc5b17874245b5f88f5c2b712709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 21:16:22 -0300 Subject: [PATCH 09/16] Fix alpha overwrite --- src/Colorfy.jl | 3 ++- test/runtests.jl | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Colorfy.jl b/src/Colorfy.jl index 1ab1d3c..a209043 100644 --- a/src/Colorfy.jl +++ b/src/Colorfy.jl @@ -32,7 +32,8 @@ function colorfy(values; alpha=1.0, colorscheme=:viridis, colorrange=:extrema) # construct colors for valid values rcolors = repr(nonmissingvec(vs[vinds]), s, r) - vcolors = coloralpha.(rcolors, αs[vinds]) + ralphas = map(Colors.alpha, rcolors) + vcolors = coloralpha.(rcolors, αs[vinds] .* ralphas) if isempty(iinds) # all values are valid, return colors directly vcolors else # set "transparent" color for invalid values diff --git a/test/runtests.jl b/test/runtests.jl index d437030..685c91f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -162,14 +162,15 @@ using Test σs = scale.(values) a, b = extrema(σs) colors = colorfy(μs, alpha=1 .- (σs .- a) ./ (b .- a)) - colorfy(values) + alphas = map(Colors.alpha, colors) @test colorfy(values) == colors - @test colorfy(values, alpha=0.5) == coloralpha.(colors, 0.5) + @test colorfy(values, alpha=0.5) == coloralpha.(colors, 0.5 * alphas) values = [missing, Normal(0.5, 0.5), Normal(0.6, 0.6), Normal(0.7, 0.7), missing] colors = [colorant"transparent"; colorfy([0.5, 0.6, 0.7], alpha=[1.0, 0.5, 0.0]); colorant"transparent"] + alphas = map(Colors.alpha, colors) @test colorfy(values) == colors - @test colorfy(values, alpha=0.5) == coloralpha.(colors, 0.5) + @test colorfy(values, alpha=0.5) == coloralpha.(colors, 0.5 * alphas) end @testset "Unitful" begin From 6399730acc903bff6fcb88721d284f4d970301dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 21:54:03 -0300 Subject: [PATCH 10/16] Update test --- test/runtests.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 685c91f..25cee2e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -167,10 +167,9 @@ using Test @test colorfy(values, alpha=0.5) == coloralpha.(colors, 0.5 * alphas) values = [missing, Normal(0.5, 0.5), Normal(0.6, 0.6), Normal(0.7, 0.7), missing] - colors = [colorant"transparent"; colorfy([0.5, 0.6, 0.7], alpha=[1.0, 0.5, 0.0]); colorant"transparent"] - alphas = map(Colors.alpha, colors) - @test colorfy(values) == colors - @test colorfy(values, alpha=0.5) == coloralpha.(colors, 0.5 * alphas) + colors = colorfy(values) + @test first(colors) == colorant"transparent" + @test last(colors) == colorant"transparent" end @testset "Unitful" begin From 2e5182640cae2242def49334d01cf27dd637827b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 22:07:46 -0300 Subject: [PATCH 11/16] More refactoring --- src/Colorfy.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Colorfy.jl b/src/Colorfy.jl index a209043..5be0f0f 100644 --- a/src/Colorfy.jl +++ b/src/Colorfy.jl @@ -49,14 +49,17 @@ function handleargs(values, alphas, colorscheme, colorrange) vs, αs, s, r end -asvalues(values) = values -asvalues(values::AbstractVector{<:Colorant}) = values +asvalues(value::Symbol) = [value] +asvalues(value::AbstractString) = [value] asvalues(values::AbstractVector{<:Colorant{Q0f7}}) = fixcolors(values) asvalues(values::AbstractVector{<:Colorant{Q0f15}}) = fixcolors(values) asvalues(values::AbstractVector{<:Colorant{Q0f31}}) = fixcolors(values) asvalues(values::AbstractVector{<:Colorant{Q0f63}}) = fixcolors(values) +asvalues(values) = values asalphas(alpha::Number, values) = fill(alpha, length(values)) +asalphas(alpha::Number, value::Symbol) = [alpha] +asalphas(alpha::Number, value::AbstractString) = [alpha] function asalphas(alphas::AbstractVector, values) if length(alphas) ≠ length(values) throw(ArgumentError("the number of alphas must be equal to the number of values")) From efb422a8ce278b96e02dacf1a5e5885d581c7988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 22:17:03 -0300 Subject: [PATCH 12/16] More refactoring --- src/Colorfy.jl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Colorfy.jl b/src/Colorfy.jl index 5be0f0f..71f1975 100644 --- a/src/Colorfy.jl +++ b/src/Colorfy.jl @@ -34,6 +34,8 @@ function colorfy(values; alpha=1.0, colorscheme=:viridis, colorrange=:extrema) rcolors = repr(nonmissingvec(vs[vinds]), s, r) ralphas = map(Colors.alpha, rcolors) vcolors = coloralpha.(rcolors, αs[vinds] .* ralphas) + + # construct colors for all values if isempty(iinds) # all values are valid, return colors directly vcolors else # set "transparent" color for invalid values @@ -49,17 +51,13 @@ function handleargs(values, alphas, colorscheme, colorrange) vs, αs, s, r end -asvalues(value::Symbol) = [value] -asvalues(value::AbstractString) = [value] +asvalues(values) = values asvalues(values::AbstractVector{<:Colorant{Q0f7}}) = fixcolors(values) asvalues(values::AbstractVector{<:Colorant{Q0f15}}) = fixcolors(values) asvalues(values::AbstractVector{<:Colorant{Q0f31}}) = fixcolors(values) asvalues(values::AbstractVector{<:Colorant{Q0f63}}) = fixcolors(values) -asvalues(values) = values asalphas(alpha::Number, values) = fill(alpha, length(values)) -asalphas(alpha::Number, value::Symbol) = [alpha] -asalphas(alpha::Number, value::AbstractString) = [alpha] function asalphas(alphas::AbstractVector, values) if length(alphas) ≠ length(values) throw(ArgumentError("the number of alphas must be equal to the number of values")) From c8d14be1771268b3eeb002846290d0ca7c73c9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 22:22:21 -0300 Subject: [PATCH 13/16] More refactoring --- test/runtests.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 25cee2e..d774c02 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -168,8 +168,11 @@ using Test values = [missing, Normal(0.5, 0.5), Normal(0.6, 0.6), Normal(0.7, 0.7), missing] colors = colorfy(values) - @test first(colors) == colorant"transparent" - @test last(colors) == colorant"transparent" + @test colors[1] == colorant"transparent" + @test colors[2] != colorant"transparent" + @test colors[3] != colorant"transparent" + @test colors[4] != colorant"transparent" + @test colors[5] == colorant"transparent" end @testset "Unitful" begin From bea0d22a8653dce73d08a58980af2ae9765df9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 22:39:43 -0300 Subject: [PATCH 14/16] Implement copilot suggestions --- src/Colorfy.jl | 15 +++++++++++++-- test/runtests.jl | 14 +++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/Colorfy.jl b/src/Colorfy.jl index 71f1975..e3bb407 100644 --- a/src/Colorfy.jl +++ b/src/Colorfy.jl @@ -30,6 +30,9 @@ function colorfy(values; alpha=1.0, colorscheme=:viridis, colorrange=:extrema) iinds = findall(isinvalid, vs) vinds = setdiff(1:length(vs), iinds) + # if all values are invalid, return transparent colors + isempty(vinds) && return fill(colorant"transparent", length(values)) + # construct colors for valid values rcolors = repr(nonmissingvec(vs[vinds]), s, r) ralphas = map(Colors.alpha, rcolors) @@ -83,7 +86,14 @@ ascolorrange(colorrange::NTuple{2,Real}) = promote(colorrange...) Colorful representation of `values` of type `T` based on `colorscheme` and `colorrange`. """ -function repr end +function repr(values::AbstractVector{T}, colorscheme, colorrange) where {T} + throw(ArgumentError(""" + values of type `$T` do not have a colorful representation. + + Please make sure your vector has a concrete element type + and that a `Colorfy.repr` method exists for it. + """)) +end repr(values::AbstractVector{<:Colorant}, colorscheme, colorrange) = values @@ -95,7 +105,8 @@ repr(values::AbstractVector{<:AbstractString}, colorscheme, colorrange) = parse. repr(values::AbstractVector{<:Date}, colorscheme, colorrange) = repr(DateTime.(values), colorscheme, colorrange) -repr(values::AbstractVector{<:DateTime}, colorscheme, colorrange) = repr(datetime2unix.(values), colorscheme, colorrange) +repr(values::AbstractVector{<:DateTime}, colorscheme, colorrange) = + repr(datetime2unix.(values), colorscheme, colorrange) # ----------------- # HELPER FUNCTIONS diff --git a/test/runtests.jl b/test/runtests.jl index d774c02..a6bef2f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -103,7 +103,7 @@ using Test colors2 = colorfy(values, alpha=alphas) @test colors2 == coloralpha.(colors1, alphas) - # invalid values (missing, NaN, Inf) should be colored as "transparent" + # invalid values (missing, NaN, Inf) are made transparent values = [0.1, missing, 0.2, NaN, 0.3, Inf, 0.4, -Inf, 0.5] colors = colorfy(values, alpha=0.5) @test colors[2] == colorant"transparent" @@ -111,15 +111,23 @@ using Test @test colors[6] == colorant"transparent" @test colors[8] == colorant"transparent" + # if all values are invalid, return transparent colors + values = [missing, NaN, Inf, -Inf] + colors = colorfy(values) + @test all(c -> c == colorant"transparent", colors) + values = [missing, missing, missing, missing] + colors = colorfy(values) + @test all(c -> c == colorant"transparent", colors) + # Vector{Union{Missing,T}} whitout missing values values = Union{Missing,Int}[1, 2, 3, 4, 5] @test colorfy(values) == colorfy([1, 2, 3, 4, 5]) # error: unsupported values values = [nothing, nothing, nothing] - @test_throws MethodError colorfy(values) + @test_throws ArgumentError colorfy(values) values = Any[:red, :green, :blue] # vector with non-concrete eltype - @test_throws MethodError colorfy(values) + @test_throws ArgumentError colorfy(values) end @testset "Dates" begin From 993fb9fd6e39190530a5fc2abd74db4f9de65922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 22:42:11 -0300 Subject: [PATCH 15/16] Apply more copilot suggestions --- README.md | 4 ++-- ext/ColorfyDistributionsExt.jl | 4 ++-- test/runtests.jl | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 011291d..aee7b1b 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ defined by [Colors.jl](https://github.com/JuliaGraphics/Colors.jl). ## Usage -The `colorfy` function takes any vector of values with options -(e.g., alpha, colorscheme) and converts into valid colors: +The `colorfy` function takes a vector of values with options +(e.g., `alpha`, `colorscheme`) and converts them into valid colors: ```julia julia> values = [:red, :green, :blue]; diff --git a/ext/ColorfyDistributionsExt.jl b/ext/ColorfyDistributionsExt.jl index c7a8f1e..c53fd00 100644 --- a/ext/ColorfyDistributionsExt.jl +++ b/ext/ColorfyDistributionsExt.jl @@ -18,9 +18,9 @@ function Colorfy.repr(values::AbstractVector{<:Distribution}, colorscheme, color # compute alphas based on scale parameters a, b = extrema(σs) αs = if a == b - fill(1, length(μs)) + fill(1.0, length(μs)) else - @. 1 - (σs - a) / (b - a) + @. 1.0 - (σs - a) / (b - a) end # get colors for location parameters diff --git a/test/runtests.jl b/test/runtests.jl index a6bef2f..fbe85a6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -119,7 +119,7 @@ using Test colors = colorfy(values) @test all(c -> c == colorant"transparent", colors) - # Vector{Union{Missing,T}} whitout missing values + # Vector{Union{Missing,T}} without missing values values = Union{Missing,Int}[1, 2, 3, 4, 5] @test colorfy(values) == colorfy([1, 2, 3, 4, 5]) From 5fdf1c3a9e3e24edd267e597306af00a175d60ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Fri, 12 Jun 2026 22:49:04 -0300 Subject: [PATCH 16/16] Improve docstring --- src/Colorfy.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Colorfy.jl b/src/Colorfy.jl index e3bb407..9bb46ee 100644 --- a/src/Colorfy.jl +++ b/src/Colorfy.jl @@ -18,9 +18,9 @@ Convert `values` to Colors.jl colors based on given options. ## Options -* `alpha` - Scalar or vector of transparency values -* `colorscheme` - Color scheme from ColorSchemes.jl -* `colorrange` - Minimum and maximum values or a symbol +* `alpha` - Scalar or vector of transparency values between 0.0 and 1.0 +* `colorscheme` - Color scheme from ColorSchemes.jl (e.g., "viridis", ["black", "white"]) +* `colorrange` - Minimum and maximum values or a symbol (see `ColorSchemes.get`) """ function colorfy(values; alpha=1.0, colorscheme=:viridis, colorrange=:extrema) # handle input arguments