Skip to content

Commit b60d284

Browse files
authored
Handle colorscheme to colormap conversion (#49)
* Handle colorscheme to colormap conversion * More fixes
1 parent 15e706b commit b60d284

5 files changed

Lines changed: 110 additions & 86 deletions

File tree

ext/ColorfyCategoricalArraysExt.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ using CategoricalArrays: levels, levelcode
99

1010
import Colorfy
1111

12-
function Colorfy.repr(values::AbstractVector{<:CategoricalValue}, colorscheme, colorrange)
12+
function Colorfy.repr(values::AbstractVector{<:CategoricalValue}, colormap, colorrange)
1313
# not all levels may be present in the input values,
1414
# so we need to get the number of levels from the type
15-
n = length(levels(values))
16-
c = get(colorscheme, 1:n, colorrange)
17-
c[map(levelcode, values)]
15+
n = Colorfy.nlevels(values)
16+
c = get(colormap, 1:n, colorrange)
17+
c[Colorfy.nominal(values)]
1818
end
1919

2020
Colorfy.nominal(values::AbstractVector{<:CategoricalValue}) = map(levelcode, values)
2121

22+
Colorfy.nlevels(values::AbstractVector{<:CategoricalValue}) = length(levels(values))
23+
2224
end

ext/ColorfyCoDaExt.jl

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,19 @@ using Colors: coloralpha
1010

1111
import Colorfy
1212

13-
function Colorfy.repr(values::AbstractVector{<:Composition}, colorscheme, colorrange)
14-
# compute probabilities
15-
ps = map(values) do v
16-
cs = components(v)
17-
cs ./ sum(cs)
18-
end
13+
function Colorfy.repr(values::AbstractVector{<:Composition}, colormap, colorrange)
14+
# derive base color from mode
15+
n = Colorfy.nlevels(values)
16+
c = get(colormap, 1:n, colorrange)
17+
cs = c[Colorfy.nominal(values)]
1918

2019
# compute Shannon entropy
21-
hs = map(ps) do p
20+
hs = map(values) do v
21+
c = components(v)
22+
p = c ./ sum(c)
2223
-sum(pᵢ * log(pᵢ) for pᵢ in p if pᵢ > 0)
2324
end
2425

25-
# derive base color from mode
26-
n = length(first(ps))
27-
c = get(colorscheme, 1:n, colorrange)
28-
cs = c[map(argmax, ps)]
29-
3026
# derive transparency from entropy
3127
a, b = 0.0, log(n)
3228
αs = @. 1.0 - (hs - a) / (b - a)
@@ -37,4 +33,6 @@ end
3733

3834
Colorfy.nominal(values::AbstractVector{<:Composition}) = map(argmax components, values)
3935

36+
Colorfy.nlevels(values::AbstractVector{<:Composition}) = length(components(first(values)))
37+
4038
end

ext/ColorfyDistributionsExt.jl

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@ using Colors
99

1010
import Colorfy
1111

12-
function Colorfy.repr(values::AbstractVector{<:Normal}, colorscheme, colorrange)
13-
# extract location and entropy
14-
ms = map(location, values)
15-
hs = map(entropy, values)
16-
12+
function Colorfy.repr(values::AbstractVector{<:Normal}, colormap, colorrange)
1713
# derive base colors from location
18-
cs = Colorfy.repr(ms, colorscheme, colorrange)
14+
cs = Colorfy.repr(Colorfy.nominal(values), colormap, colorrange)
15+
16+
# compute Shannon entropy
17+
hs = map(entropy, values)
1918

2019
# derive transparency from entropy
2120
a, b = extrema(hs)
@@ -29,17 +28,16 @@ function Colorfy.repr(values::AbstractVector{<:Normal}, colorscheme, colorrange)
2928
map(coloralpha, cs, αs)
3029
end
3130

32-
Colorfy.repr(values::AbstractVector{<:Dirac}, colorscheme, colorrange) =
33-
Colorfy.repr(mode.(values), colorscheme, colorrange)
34-
35-
function Colorfy.repr(values::AbstractVector{<:Bernoulli}, colorscheme, colorrange)
36-
# extract mode and entropy
37-
ms = map(mode, values)
38-
hs = map(entropy, values)
31+
Colorfy.repr(values::AbstractVector{<:Dirac}, colormap, colorrange) =
32+
Colorfy.repr(map(mode, values), colormap, colorrange) # TODO
3933

34+
function Colorfy.repr(values::AbstractVector{<:Bernoulli}, colormap, colorrange)
4035
# derive base colors from mode
41-
c = get(colorscheme, 0:1, colorrange)
42-
cs = c[ms .+ 1]
36+
c = get(colormap, 0:1, colorrange)
37+
cs = c[Colorfy.nominal(values) .+ 1]
38+
39+
# compute Shannon entropy
40+
hs = map(entropy, values)
4341

4442
# derive transparency from entropy
4543
a, b = 0.0, log(2)
@@ -49,15 +47,14 @@ function Colorfy.repr(values::AbstractVector{<:Bernoulli}, colorscheme, colorran
4947
map(coloralpha, cs, αs)
5048
end
5149

52-
function Colorfy.repr(values::AbstractVector{<:Categorical}, colorscheme, colorrange)
53-
# extract mode and entropy
54-
ms = map(mode, values)
55-
hs = map(entropy, values)
56-
50+
function Colorfy.repr(values::AbstractVector{<:Categorical}, colormap, colorrange)
5751
# derive base colors from mode
58-
n = ncategories(first(values))
59-
c = get(colorscheme, 1:n, colorrange)
60-
cs = c[ms]
52+
n = Colorfy.nlevels(values)
53+
c = get(colormap, 1:n, colorrange)
54+
cs = c[Colorfy.nominal(values)]
55+
56+
# compute Shannon entropy
57+
hs = map(entropy, values)
6158

6259
# derive transparency from entropy
6360
a, b = 0.0, log(n)
@@ -75,4 +72,10 @@ Colorfy.nominal(values::AbstractVector{<:ContinuousUnivariateDistribution}) = Co
7572

7673
Colorfy.nominal(values::AbstractVector{<:DiscreteUnivariateDistribution}) = Colorfy.nominal(map(mode, values))
7774

75+
Colorfy.nlevels(values::AbstractVector{<:Dirac}) = 1
76+
77+
Colorfy.nlevels(values::AbstractVector{<:Bernoulli}) = 2
78+
79+
Colorfy.nlevels(values::AbstractVector{<:Categorical}) = ncategories(first(values))
80+
7881
end

ext/ColorfyUnitfulExt.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ using Unitful: ustrip
99

1010
import Colorfy
1111

12-
Colorfy.repr(values::AbstractVector{<:Quantity}, colorscheme, colorrange) =
13-
Colorfy.repr(map(ustrip, values), colorscheme, colorrange)
12+
Colorfy.repr(values::AbstractVector{<:Quantity}, colormap, colorrange) =
13+
Colorfy.repr(Colorfy.nominal(values), colormap, colorrange)
1414

1515
Colorfy.nominal(values::AbstractVector{<:Quantity}) = Colorfy.nominal(map(ustrip, values))
1616

src/Colorfy.jl

Lines changed: 66 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Convert `values` to Colors.jl colors based on given options.
2424
"""
2525
function colorfy(values; alpha=1.0, colorscheme=:viridis, colorrange=:extrema)
2626
# handle input arguments
27-
vs, αs, s, r = handleargs(values, alpha, colorscheme, colorrange)
27+
vs, αs, cm, cr = handleargs(values, alpha, colorscheme, colorrange)
2828

2929
# find invalid and valid indices
3030
iinds = findall(isinvalid, vs)
@@ -34,7 +34,7 @@ function colorfy(values; alpha=1.0, colorscheme=:viridis, colorrange=:extrema)
3434
isempty(vinds) && return fill(colorant"transparent", length(values))
3535

3636
# construct colors for valid values
37-
rcolors = repr(nonmissingvec(vs[vinds]), s, r)
37+
rcolors = repr(nonmissingvec(vs[vinds]), cm, cr)
3838
ralphas = map(Colors.alpha, rcolors)
3939
vcolors = coloralpha.(rcolors, αs[vinds] .* ralphas)
4040

@@ -49,9 +49,9 @@ end
4949
function handleargs(values, alphas, colorscheme, colorrange)
5050
vs = asvalues(values)
5151
αs = asalphas(alphas, vs)
52-
s = ascolorscheme(colorscheme)
53-
r = ascolorrange(colorrange)
54-
vs, αs, s, r
52+
cm = ascolormap(colorscheme, vs)
53+
cr = ascolorrange(colorrange)
54+
vs, αs, cm, cr
5555
end
5656

5757
asvalues(values) = values
@@ -68,6 +68,12 @@ function asalphas(alphas::AbstractVector, values)
6868
alphas
6969
end
7070

71+
function ascolormap(colorscheme, values)
72+
nl = nlevels(values)
73+
cs = ascolorscheme(colorscheme)
74+
isfinite(nl) ? discretescheme(cs, nl) : cs
75+
end
76+
7177
ascolorscheme(colorscheme::Symbol) = colorschemes[colorscheme]
7278
ascolorscheme(colorscheme::AbstractString) = ascolorscheme(Symbol(colorscheme))
7379
ascolorscheme(colorscheme::AbstractVector) = ColorScheme([parse(Colorant, color) for color in colorscheme])
@@ -79,42 +85,16 @@ function ascolorrange(colorrange::NTuple{2,Number})
7985
Tuple(nominal(collect(crange)))
8086
end
8187

82-
"""
83-
nominal(values)
84-
85-
Nominal representation of `values` for color mapping.
86-
This function is used to convert non-numeric values to
87-
numeric values that can be used in ticks and color bars.
88-
"""
89-
function nominal(values)
90-
# find invalid and valid indices
91-
iinds = findall(isinvalid, values)
92-
vinds = setdiff(1:length(values), iinds)
93-
94-
# if all values are invalid, return missing values
95-
isempty(vinds) && return fill(missing, length(values))
96-
97-
# construct nominal values for valid values
98-
vvalues = nominal(nonmissingvec(values[vinds]))
99-
100-
# construct nominal values for all values
101-
if isempty(iinds) # all values are valid, return nominal values directly
102-
vvalues
103-
else # set missing value for invalid values
104-
genvec(vinds, vvalues, iinds, missing)
105-
end
106-
end
107-
10888
# ----------------
10989
# IMPLEMENTATIONS
11090
# ----------------
11191

11292
"""
113-
repr(values::AbstractVector{T}, colorscheme, colorrange)
93+
repr(values, colormap, colorrange)
11494
115-
Colorful representation of `values` of type `T` based on `colorscheme` and `colorrange`.
95+
Colorful representation of `values` based on `colormap` and `colorrange`.
11696
"""
117-
function repr(values::AbstractVector{T}, colorscheme, colorrange) where {T}
97+
function repr(values::AbstractVector{T}, colormap, colorrange) where {T}
11898
throw(ArgumentError("""
11999
values of type `$T` do not have a colorful representation.
120100
@@ -123,30 +103,57 @@ function repr(values::AbstractVector{T}, colorscheme, colorrange) where {T}
123103
"""))
124104
end
125105

126-
repr(values::AbstractVector{<:Colorant}, colorscheme, colorrange) = values
106+
repr(values::AbstractVector{<:Colorant}, colormap, colorrange) = values
127107

128-
function repr(values::AbstractVector{<:Number}, colorscheme, colorrange)
108+
function repr(values::AbstractVector{<:Number}, colormap, colorrange)
129109
isna(v) = isnan(v) || isinf(v)
130110
if any(isna, values)
131111
iinds = findall(isna, values)
132112
vinds = setdiff(1:length(values), iinds)
133113
vvals = nonmissingvec(values[vinds])
134-
vcolor = get(colorscheme, vvals, colorrange)
114+
vcolor = get(colormap, vvals, colorrange)
135115
icolor = colorant"transparent"
136116
genvec(vinds, vcolor, iinds, icolor)
137117
else
138-
get(colorscheme, values, colorrange)
118+
get(colormap, values, colorrange)
139119
end
140120
end
141121

142-
repr(values::AbstractVector{<:Symbol}, colorscheme, colorrange) = repr(map(string, values), colorscheme, colorrange)
122+
repr(values::AbstractVector{<:Symbol}, colormap, colorrange) = repr(map(string, values), colormap, colorrange)
123+
124+
repr(values::AbstractVector{<:AbstractString}, colormap, colorrange) = map(v -> parse(Colorant, v), values)
143125

144-
repr(values::AbstractVector{<:AbstractString}, colorscheme, colorrange) = map(v -> parse(Colorant, v), values)
126+
repr(values::AbstractVector{<:Date}, colormap, colorrange) = repr(map(DateTime, values), colormap, colorrange)
145127

146-
repr(values::AbstractVector{<:Date}, colorscheme, colorrange) = repr(map(DateTime, values), colorscheme, colorrange)
128+
repr(values::AbstractVector{<:DateTime}, colormap, colorrange) =
129+
repr(map(datetime2unix, values), colormap, colorrange)
147130

148-
repr(values::AbstractVector{<:DateTime}, colorscheme, colorrange) =
149-
repr(map(datetime2unix, values), colorscheme, colorrange)
131+
"""
132+
nominal(values)
133+
134+
Nominal representation of `values` for color mapping.
135+
136+
This function is used to convert non-numeric values to
137+
numeric values that can be used in ticks and color bars.
138+
"""
139+
function nominal(values)
140+
# find invalid and valid indices
141+
iinds = findall(isinvalid, values)
142+
vinds = setdiff(1:length(values), iinds)
143+
144+
# if all values are invalid, return missing values
145+
isempty(vinds) && return fill(missing, length(values))
146+
147+
# construct nominal values for valid values
148+
vvalues = nominal(nonmissingvec(values[vinds]))
149+
150+
# construct nominal values for all values
151+
if isempty(iinds) # all values are valid, return nominal values directly
152+
vvalues
153+
else # set missing value for invalid values
154+
genvec(vinds, vvalues, iinds, missing)
155+
end
156+
end
150157

151158
function nominal(values::AbstractVector{<:Number})
152159
isna(v) = isnan(v) || isinf(v)
@@ -164,14 +171,28 @@ nominal(values::AbstractVector{<:Date}) = nominal(map(DateTime, values))
164171

165172
nominal(values::AbstractVector{<:DateTime}) = map(datetime2unix, values)
166173

174+
"""
175+
nlevels(values)
176+
177+
Number of levels in `values` for color mapping.
178+
179+
This function is used to determine the number of
180+
colors needed for categorical data. By default,
181+
it returns `Inf` to indicate that the number of
182+
levels is infinite (i.e., continuous data).
183+
"""
184+
nlevels(values) = Inf
185+
167186
# -----------------
168187
# HELPER FUNCTIONS
169188
# -----------------
170189

171-
isinvalid(value) = ismissing(value) || (value isa Number && !isfinite(value))
172-
173190
fixcolors(colors) = convert.(floattype(eltype(colors)), colors)
174191

192+
discretescheme(colorscheme, n) = colorscheme # TODO
193+
194+
isinvalid(value) = ismissing(value) || (value isa Number && !isfinite(value))
195+
175196
nonmissingvec(values::AbstractVector{T}) where {T} = convert(AbstractVector{nonmissingtype(T)}, values)
176197

177198
function genvec(vecinds, vec, valinds, val)

0 commit comments

Comments
 (0)