Series handling

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

_prepare_series_data(x) = error("Cannot convert $(typeof(x)) to series data for plotting")
_prepare_series_data(::Nothing) = nothing
_prepare_series_data(t::Tuple{T, T}) where {T <: Number} = t
_prepare_series_data(f::Function) = f
_prepare_series_data(ar::AbstractRange{<:Number}) = ar
function _prepare_series_data(a::AbstractArray{T}) where {T<:MaybeNumber}

Get a non-missing AbstractFloat type for the array There may be a better way to do this?

    F = typeof(float(zero(nonmissingtype(T))))

Create a new array with this type to write to

    float_a = similar(a, F)

Replace missing and inf values with NaN

    broadcast!(float_a, a) do x
        ismissing(x) || isinf(x) ? NaN : x
    end
    return float_a
end
_prepare_series_data(a::AbstractArray{<:Missing}) = fill(NaN, axes(a))
_prepare_series_data(a::AbstractArray{<:MaybeString}) =
    replace(x -> ismissing(x) ? "" : x, a)
_prepare_series_data(s::Surface{<:AMat{<:MaybeNumber}}) =
    Surface(_prepare_series_data(s.surf))
_prepare_series_data(s::Surface) = s  # non-numeric Surface, such as an image
_prepare_series_data(v::Volume) =
    Volume(_prepare_series_data(v.v), v.x_extents, v.y_extents, v.z_extents)

default: assume x represents a single series

_series_data_vector(x, plotattributes) = [_prepare_series_data(x)]

fixed number of blank series

_series_data_vector(n::Integer, plotattributes) = [zeros(0) for i in 1:n]

vector of data points is a single series

_series_data_vector(v::AVec{<:DataPoint}, plotattributes) = [_prepare_series_data(v)]

list of things (maybe other vectors, functions, or something else)

function _series_data_vector(v::AVec, plotattributes)
    if all(x -> x isa MaybeNumber, v)
        _series_data_vector(Vector{MaybeNumber}(v), plotattributes)
    elseif all(x -> x isa MaybeString, v)
        _series_data_vector(Vector{MaybeString}(v), plotattributes)
    else
        vcat((_series_data_vector(vi, plotattributes) for vi in v)...)
    end
end

Matrix is split into columns

function _series_data_vector(v::AMat{<:DataPoint}, plotattributes)
    if is3d(plotattributes)
        [_prepare_series_data(Surface(v))]
    else
        [_prepare_series_data(v[:, i]) for i in axes(v, 2)]
    end
end

_compute_x(x::Nothing, y::Nothing, z) = axes(z, 1)
_compute_x(x::Nothing, y, z) = axes(y, 1)
_compute_x(x::Function, y, z) = map(x, y)
_compute_x(x, y, z) = x

_compute_y(x::Nothing, y::Nothing, z) = axes(z, 2)
_compute_y(x, y::Function, z) = map(y, x)
_compute_y(x, y, z) = y

_compute_z(x, y, z::Function) = map(z, x, y)
_compute_z(x, y, z::AbstractMatrix) = Surface(z)
_compute_z(x, y, z::Nothing) = nothing
_compute_z(x, y, z) = z

_nobigs(v::AVec{BigFloat}) = map(Float64, v)
_nobigs(v::AVec{BigInt}) = map(Int64, v)
_nobigs(v) = v

@noinline function _compute_xyz(x, y, z, nice_error=false)
    x = _compute_x(x, y, z)
    y = _compute_y(x, y, z)
    z = _compute_z(x, y, z)
    if nice_error && isnothing(z) # don't touch 3D plots
        n = size(x,1)
        !isnothing(y) && size(y,1) != n && error("Expects $n elements in each col of y, found $(size(y,1)).")
    end
    _nobigs(x), _nobigs(y), _nobigs(z)
end

not allowed

_compute_xyz(x::Nothing, y::FuncOrFuncs{F}, z) where {F <: Function} =
    error("If you want to plot the function `$y`, you need to define the x values!")
_compute_xyz(x::Nothing, y::Nothing, z::FuncOrFuncs{F}) where {F <: Function} =
    error("If you want to plot the function `$z`, you need to define x and y values!")
_compute_xyz(x::Nothing, y::Nothing, z::Nothing) = error("x/y/z are all nothing!")

we are going to build recipes to do the processing and splitting of the args


The catch-all SliceIt recipe

ensure we dispatch to the slicer

struct SliceIt end

The SliceIt recipe finishes user and type recipe processing. It splits processed data into individual series data, stores in copied plotattributes for each series and returns no arguments.

@recipe function f(::Type{SliceIt}, x, y, z)
    @nospecialize
    nice_error = (x isa AbstractVector) && (y isa AbstractMatrix) # only check in the trivial case

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 = _series_data_vector(x, plotattributes)
    ys = _series_data_vector(y, plotattributes)
    zs = _series_data_vector(z, plotattributes)


    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, nice_error)

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

This page was generated using Literate.jl.