Looking at the source code of contour
:
function plot!(plot::Contour{<:Tuple{X, Y, Z, Vol}}) where {X, Y, Z, Vol}
x, y, z, volume = plot[1:4]
@extract plot (colormap, levels, linewidth, alpha)
valuerange = lift(nan_extrema, plot, volume)
cliprange = map((v, default) -> ifelse(v === automatic, default, v), plot, plot.colorrange, valuerange)
cmap = lift(plot, colormap, levels, alpha, cliprange, valuerange) do _cmap, l, alpha, cliprange, vrange
levels = to_levels(l, vrange)
nlevels = length(levels)
N = 50 * nlevels
iso_eps = if haskey(plot, :isorange)
plot.isorange[]
else
nlevels * ((vrange[2] - vrange[1]) / N) # TODO calculate this
end
cmap = to_colormap(_cmap)
v_interval = cliprange[1] .. cliprange[2]
# resample colormap and make the empty area between iso surfaces transparent
map(1:N) do i
i01 = (i - 1) / (N - 1)
c = Makie.interpolated_getindex(cmap, i01)
isoval = vrange[1] + (i01 * (vrange[2] - vrange[1]))
line = reduce(levels, init = false) do v0, level
isoval in v_interval || return false
v0 || abs(level - isoval) <= iso_eps
end
RGBAf(Colors.color(c), line ? alpha : 0.0)
end
end
return volume!(
plot, Attributes(plot), x, y, z, volume, alpha = 1.0, # don't apply alpha 2 times
algorithm = 7, colorrange = cliprange, colormap = cmap
)
end
I guess the idea (for some reason, it seems quite inefficient) is that we check for ‘all’ values isoval
between the minimum and maximum value of volume
whether it lies sufficiently close (according to iso_eps
) to one of the supplied levels
. If so, we draw it using volume!
(the source code of which I fail to locate) in a certain colour. (Now, in practice, we cannot check all possible values of course, so we restrict ourselves to a linear grid of N
steps. If this grid is too coarse (for a given iso_eps
), we might miss a certain level
. Makie does not seem to supply any way to directly control N
, but by supplying e.g. [0.1, 0.1, 0.1]
instead of [0.1]
for levels
you do triple N
. So getting a too coarse grid can be easily circumvented.)
Now suppose you would actually check all possible isoval
s. Then your shells would have a thickness of 2 * iso_eps
. So we want to reduce iso_eps
. But as far as I can tell, there’s currently no accessible way to do this. There are some hacky solutions possible though. I’m not fond of any of them as they constitute type piracy, but, hey, they do kind of work.
Overwrite Makie.plot!
Just increase 50
in the definition of N
to e.g. 100
:
using Makie: nan_extrema, automatic, to_levels, Colors
function Makie.plot!(plot::Contour{<:Tuple{X, Y, Z, Vol}}) where {X, Y, Z, Vol}
x, y, z, volume = plot[1:4]
@extract plot (colormap, levels, linewidth, alpha)
valuerange = lift(nan_extrema, plot, volume)
cliprange = map((v, default) -> ifelse(v === automatic, default, v), plot, plot.colorrange, valuerange)
cmap = lift(plot, colormap, levels, alpha, cliprange, valuerange) do _cmap, l, alpha, cliprange, vrange
levels = to_levels(l, vrange)
nlevels = length(levels)
N = 100 * nlevels
iso_eps = if haskey(plot, :isorange)
plot.isorange[]
else
nlevels * ((vrange[2] - vrange[1]) / N) # TODO calculate this
end
cmap = to_colormap(_cmap)
v_interval = cliprange[1] .. cliprange[2]
# resample colormap and make the empty area between iso surfaces transparent
map(1:N) do i
i01 = (i - 1) / (N - 1)
c = Makie.interpolated_getindex(cmap, i01)
isoval = vrange[1] + (i01 * (vrange[2] - vrange[1]))
line = reduce(levels, init = false) do v0, level
isoval in v_interval || return false
v0 || abs(level - isoval) <= iso_eps
end
RGBAf(Colors.color(c), line ? alpha : 0.0)
end
end
return volume!(
plot, Attributes(plot), x, y, z, volume, alpha = 1.0, # don't apply alpha 2 times
algorithm = 7, colorrange = cliprange, colormap = cmap
)
end
'Add' isorange
(As a side note, snake case would make clearer we’re not talking about colours or fruits here
.)
function Base.haskey(plot::Contour{<:Tuple{X, Y, Z, Vol}}, symb::Symbol) where {X, Y, Z, Vol}
symb === :isorange && return true
invoke(haskey, Tuple{Plot, Symbol}, plot, symb)
end
function Base.getproperty(plot::Contour{<:Tuple{X, Y, Z, Vol}}, symb::Symbol) where {X, Y, Z, Vol}
symb == :isorange && return 0.01
return invoke(Base.getproperty, Tuple{Plot, Symbol}, plot, symb)
end
function new_iso_contour3d!(...)
(...)
contour!(...; levels=[lev, lev, lev], ...)
(...)
end
Results, with alpha=1
for clarity:
(Before, overwriting Makie.plot!
, ‘adding’ isorange
)