Skip to content

Commit

Permalink
Merge pull request #21 from JuliaPlots/bbs/vc2
Browse files Browse the repository at this point in the history
add default recipes
  • Loading branch information
mkborregaard committed Mar 16, 2020
2 parents 5e68f09 + 1614aed commit 7221e30
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 0 deletions.
1 change: 1 addition & 0 deletions RecipesPipeline/src/RecipePipeline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ module RecipePipeline
import RecipesBase
include("pipeline.jl")
include("process_recipes.jl")
include("default_recipes.jl")

end # module
184 changes: 184 additions & 0 deletions RecipesPipeline/src/default_recipes.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# aliases
const AVec = AbstractVector
const AMat = AbstractMatrix

# ensure we dispatch to the slicer
struct SliceIt end

"Represents data values with formatting that should apply to the tick labels."
struct Formatted{T}
data::T
formatter::Function
end

abstract type AbstractSurface end

"represents a contour or surface mesh"
struct Surface{M<:AMat} <: AbstractSurface
surf::M
end

Surface(f::Function, x, y) = Surface(Float64[f(xi,yi) for yi in y, xi in x])

Base.Array(surf::Surface) = surf.surf

for f in (:length, :size)
@eval Base.$f(surf::Surface, args...) = $f(surf.surf, args...)
end
Base.copy(surf::Surface) = Surface(copy(surf.surf))
Base.eltype(surf::Surface{T}) where {T} = eltype(T)
#---
struct Volume{T}
v::Array{T,3}
x_extents::Tuple{T,T}
y_extents::Tuple{T,T}
z_extents::Tuple{T,T}
end

default_extents(::Type{T}) where {T} = (zero(T), one(T))

function Volume(v::Array{T,3},
x_extents = default_extents(T),
y_extents = default_extents(T),
z_extents = default_extents(T)) where T
Volume(v, x_extents, y_extents, z_extents)
end

Base.Array(vol::Volume) = vol.v
for f in (:length, :size)
@eval Base.$f(vol::Volume, args...) = $f(vol.v, args...)
end
Base.copy(vol::Volume{T}) where {T} = Volume{T}(copy(vol.v), vol.x_extents, vol.y_extents, vol.z_extents)
Base.eltype(vol::Volume{T}) where {T} = T

# -----------------------------------------------------------------------
# the catch-all recipes
RecipesBase.@recipe function f(::Type{SliceIt}, x, y, z)

# handle data with formatting attached
if typeof(x) <: Formatted
xformatter := x.formatter
x = x.data
end
if typeof(y) <: Formatted
yformatter := y.formatter
y = y.data
end
if typeof(z) <: Formatted
zformatter := z.formatter
z = z.data
end

xs = convertToAnyVector(x, plotattributes)
ys = convertToAnyVector(y, plotattributes)
zs = convertToAnyVector(z, plotattributes)


fr = pop!(plotattributes, :fillrange, nothing)
fillranges = process_fillrange(fr, plotattributes)
mf = length(fillranges)

rib = pop!(plotattributes, :ribbon, nothing)
ribbons = process_ribbon(rib, plotattributes)
mr = length(ribbons)

mx = length(xs)
my = length(ys)
mz = length(zs)
if mx > 0 && my > 0 && mz > 0
for i in 1:max(mx, my, mz)
# add a new series
di = copy(plotattributes)
xi, yi, zi = xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]
di[:x], di[:y], di[:z] = compute_xyz(xi, yi, zi)

# handle fillrange
fr = fillranges[mod1(i,mf)]
di[:fillrange] = isa(fr, Function) ? map(fr, di[:x]) : fr

# handle ribbons
rib = ribbons[mod1(i,mr)]
di[:ribbon] = isa(rib, Function) ? map(rib, di[:x]) : rib

push!(series_list, RecipeData(di, ()))
end
end
nothing # don't add a series for the main block
end

# this is the default "type recipe"... just pass the object through
RecipesBase.@recipe f(::Type{T}, v::T) where {T<:Any} = v

# this should catch unhandled "series recipes" and error with a nice message
RecipesBase.@recipe f(::Type{V}, x, y, z) where {V<:Val} = error("The backend must not support the series type $V, and there isn't a series recipe defined.")

# create a new "build_series_args" which converts all inputs into xs = Any[xitems], ys = Any[yitems].
# Special handling for: no args, xmin/xmax, parametric, dataframes
# Then once inputs have been converted, build the series args, map functions, etc.
# This should cut down on boilerplate code and allow more focused dispatch on type
# note: returns meta information... mainly for use with automatic labeling from DataFrames for now

const FuncOrFuncs{F} = Union{F, Vector{F}, Matrix{F}}
const MaybeNumber = Union{Number, Missing}
const MaybeString = Union{AbstractString, Missing}
const DataPoint = Union{MaybeNumber, MaybeString}

prepareSeriesData(x) = error("Cannot convert $(typeof(x)) to series data for plotting")
prepareSeriesData(::Nothing) = nothing
prepareSeriesData(t::Tuple{T, T}) where {T<:Number} = t
prepareSeriesData(f::Function) = f
prepareSeriesData(a::AbstractArray{<:MaybeNumber}) = replace!(
x -> ismissing(x) || isinf(x) ? NaN : x,
map(float,a))
prepareSeriesData(a::AbstractArray{<:MaybeString}) = replace(x -> ismissing(x) ? "" : x, a)
prepareSeriesData(s::Surface{<:AMat{<:MaybeNumber}}) = Surface(prepareSeriesData(s.surf))
prepareSeriesData(s::Surface) = s # non-numeric Surface, such as an image
prepareSeriesData(v::Volume) = Volume(prepareSeriesData(v.v), v.x_extents, v.y_extents, v.z_extents)

# default: assume x represents a single series
convertToAnyVector(x, plotattributes) = Any[prepareSeriesData(x)]

# fixed number of blank series
convertToAnyVector(n::Integer, plotattributes) = Any[zeros(0) for i in 1:n]

# vector of data points is a single series
convertToAnyVector(v::AVec{<:DataPoint}, plotattributes) = Any[prepareSeriesData(v)]

# list of things (maybe other vectors, functions, or something else)
function convertToAnyVector(v::AVec, plotattributes)
if all(x -> x isa MaybeNumber, v)
convertToAnyVector(Vector{MaybeNumber}(v), plotattributes)
elseif all(x -> x isa MaybeString, v)
convertToAnyVector(Vector{MaybeString}(v), plotattributes)
else
vcat((convertToAnyVector(vi, plotattributes) for vi in v)...)
end
end

# Matrix is split into columns
function convertToAnyVector(v::AMat{<:DataPoint}, plotattributes)
if all3D(plotattributes)
Any[prepareSeriesData(Surface(v))]
else
Any[prepareSeriesData(v[:, i]) for i in axes(v, 2)]
end
end

# --------------------------------------------------------------------
# Fillranges & ribbons


process_fillrange(range::Number, plotattributes) = [range]
process_fillrange(range, plotattributes) = convertToAnyVector(range, plotattributes)

process_ribbon(ribbon::Number, plotattributes) = [ribbon]
process_ribbon(ribbon, plotattributes) = convertToAnyVector(ribbon, plotattributes)
# ribbon as a tuple: (lower_ribbons, upper_ribbons)
process_ribbon(ribbon::Tuple{Any,Any}, plotattributes) = collect(zip(convertToAnyVector(ribbon[1], plotattributes),
convertToAnyVector(ribbon[2], plotattributes)))


all3D(plotattributes) = trueOrAllTrue(st -> st in (:contour, :contourf, :heatmap, :surface, :wireframe, :contour3d, :image, :plots_heatmap), get(plotattributes, :seriestype, :none))

trueOrAllTrue(f::Function, x::AbstractArray) = all(f, x)
trueOrAllTrue(f::Function, x) = f(x)

0 comments on commit 7221e30

Please sign in to comment.