Merge pull request #3745 from t-bltg/mesh3d

Move mesh3d triangle to utils - gaston: implement :mesh3d
This commit is contained in:
t-bltg 2021-08-03 20:57:45 +02:00 committed by GitHub
commit c171cd0cf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 136 additions and 161 deletions

View File

@ -47,12 +47,14 @@ end
function _update_min_padding!(sp::Subplot{GastonBackend}) function _update_min_padding!(sp::Subplot{GastonBackend})
sp.minpad = 0mm, 0mm, 0mm, 0mm sp.minpad = 0mm, 0mm, 0mm, 0mm
nothing
end end
function _update_plot_object(plt::Plot{GastonBackend}) function _update_plot_object(plt::Plot{GastonBackend})
# respect the layout ratio # respect the layout ratio
dat = gaston_multiplot_pos_size(plt.layout, (0, 0, 1, 1)) dat = gaston_multiplot_pos_size(plt.layout, (0, 0, 1, 1))
gaston_multiplot_pos_size!(dat) gaston_multiplot_pos_size!(dat)
nothing
end end
for (mime, term) ( for (mime, term) (
@ -224,6 +226,9 @@ function gaston_add_series(plt::Plot{GastonBackend}, series::Series)
length(y) == size(z, 1) + 1 && (y = @view y[1:end-1]) length(y) == size(z, 1) + 1 && (y = @view y[1:end-1])
end end
end end
if st == :mesh3d
x, y, z = mesh3d_triangles(x, y, z, series[:connections])
end
for sc gaston_seriesconf!(sp, series, 1, true) for sc gaston_seriesconf!(sp, series, 1, true)
push!(curves, Gaston.Curve(x, y, z, nothing, sc)) push!(curves, Gaston.Curve(x, y, z, nothing, sc))
end end
@ -302,7 +307,7 @@ function gaston_seriesconf!(sp::Subplot{GastonBackend}, series::Series, i::Int,
palette = gaston_palette(series[:seriescolor]) palette = gaston_palette(series[:seriescolor])
gsp.axesconf *= "\nset palette model RGB defined $palette" gsp.axesconf *= "\nset palette model RGB defined $palette"
st == :heatmap && (gsp.axesconf *= "\nset view map") st == :heatmap && (gsp.axesconf *= "\nset view map")
elseif st == :wireframe elseif st (:wireframe, :mesh3d)
lc, dt, lw = gaston_lc_ls_lw(series, clims, i) lc, dt, lw = gaston_lc_ls_lw(series, clims, i)
push!(curveconf, "w lines lc $lc dt $dt lw $lw") push!(curveconf, "w lines lc $lc dt $dt lw $lw")
elseif st == :quiver elseif st == :quiver
@ -467,14 +472,6 @@ end
# Helpers # Helpers
# -------------------------------------------- # --------------------------------------------
function gaston_font(f; rot=true, align=true, color=true, scale=1)
font = String["font '$(f.family),$(round(Int, scale * f.pointsize))'"]
align && push!(font, "$(gaston_halign(f.halign))")
rot && push!(font, "rotate by $(f.rotation)")
color && push!(font, "textcolor $(gaston_color(f.color))")
return join(font, " ")
end
gaston_halign(k) = (left=:left, hcenter=:center, right=:right)[k] gaston_halign(k) = (left=:left, hcenter=:center, right=:right)[k]
gaston_valign(k) = (top=:top, vcenter=:center, bottom=:bottom)[k] gaston_valign(k) = (top=:top, vcenter=:center, bottom=:bottom)[k]
@ -492,6 +489,14 @@ gaston_mk_ms_mc(series::Series, clims, i::Int) = (
gaston_color(get_markercolor(series, clims, i), get_markeralpha(series, i)), gaston_color(get_markercolor(series, clims, i), get_markeralpha(series, i)),
) )
function gaston_font(f; rot=true, align=true, color=true, scale=1)
font = String["font '$(f.family),$(round(Int, scale * f.pointsize))'"]
align && push!(font, "$(gaston_halign(f.halign))")
rot && push!(font, "rotate by $(f.rotation)")
color && push!(font, "textcolor $(gaston_color(f.color))")
return join(font, " ")
end
function gaston_palette(gradient) function gaston_palette(gradient)
palette = String[]; n = -1 palette = String[]; n = -1
for rgba gradient # FIXME: naive conversion, inefficient ? for rgba gradient # FIXME: naive conversion, inefficient ?

View File

@ -1869,35 +1869,8 @@ function gr_draw_surface(series, x, y, z, clims)
GR.surface(x, y, z, get(e_kwargs, :display_option, GR.OPTION_FILLED_MESH)) GR.surface(x, y, z, get(e_kwargs, :display_option, GR.OPTION_FILLED_MESH))
elseif st === :mesh3d elseif st === :mesh3d
@warn "GR: mesh3d is experimental (no face colors)" @warn "GR: mesh3d is experimental (no face colors)"
conn = series[:connections]
if typeof(conn) <: Tuple{Array, Array, Array}
ci, cj, ck = conn
if !(length(ci) == length(cj) == length(ck))
throw(ArgumentError("Argument connections must consist of equally sized arrays."))
end
else
throw(ArgumentError("Argument connections has to be a tuple of three arrays."))
end
X = zeros(eltype(x), 4length(ci))
Y = zeros(eltype(y), 4length(cj))
Z = zeros(eltype(z), 4length(ck))
@inbounds for I 1:length(ci)
i = ci[I] + 1 # connections are 0-based
j = cj[I] + 1
k = ck[I] + 1
m = 4(I - 1) + 1; n = m + 1; o = m + 2; p = m + 3
X[m] = X[p] = x[i]
Y[m] = Y[p] = y[i]
Z[m] = Z[p] = z[i]
X[n] = x[j]
Y[n] = y[j]
Z[n] = z[j]
X[o] = x[k]
Y[o] = y[k]
Z[o] = z[k]
end
gr_set_line(get_linewidth(series), get_linestyle(series), get_linecolor(series), series) gr_set_line(get_linewidth(series), get_linestyle(series), get_linecolor(series), series)
GR.polyline3d(X, Y, Z) GR.polyline3d(mesh3d_triangles(x, y, z, series[:connections])...)
else else
throw(ArgumentError("Not handled !")) throw(ArgumentError("Not handled !"))
end end

View File

@ -1247,7 +1247,6 @@ _backend_skips = Dict(
:gaston => [ :gaston => [
2, # animations 2, # animations
31, # animations 31, # animations
47, # TODO: support mesh3d
49, # TODO: support polar 49, # TODO: support polar
50, # TODO: 1D data not supported for pm3d 50, # TODO: 1D data not supported for pm3d
], ],

View File

@ -1,6 +1,7 @@
function treats_y_as_x(seriestype) # ---------------------------------------------------------------
return seriestype in (:vline, :vspan, :histogram, :barhist, :stephist, :scatterhist)
end treats_y_as_x(seriestype) = seriestype in (:vline, :vspan, :histogram, :barhist, :stephist, :scatterhist)
function replace_image_with_heatmap(z::Array{T}) where T<:Colorant function replace_image_with_heatmap(z::Array{T}) where T<:Colorant
n, m = size(z) n, m = size(z)
colors = palette(vec(z)) colors = palette(vec(z))
@ -143,7 +144,7 @@ allnan(istart::Int, iend::Int, args::Tuple) = all(anynan(args), istart:iend)
function Base.iterate(itr::NaNSegmentsIterator, nextidx::Int = itr.n1) function Base.iterate(itr::NaNSegmentsIterator, nextidx::Int = itr.n1)
i = findfirst(!anynan(itr.args), nextidx:itr.n2) i = findfirst(!anynan(itr.args), nextidx:itr.n2)
i === nothing && return nothing i === nothing && return
nextval = nextidx + i - 1 nextval = nextidx + i - 1
j = findfirst(anynan(itr.args), nextval:itr.n2) j = findfirst(anynan(itr.args), nextval:itr.n2)
@ -191,7 +192,7 @@ makevec(v::AVec) = v
makevec(v::T) where {T} = T[v] makevec(v::T) where {T} = T[v]
"duplicate a single value, or pass the 2-tuple through" "duplicate a single value, or pass the 2-tuple through"
maketuple(x::Real) = (x,x) maketuple(x::Real) = (x, x)
maketuple(x::Tuple{T,S}) where {T,S} = x maketuple(x::Tuple{T,S}) where {T,S} = x
for i in 2:4 for i in 2:4
@ -224,7 +225,7 @@ expand_data(v, n::Integer) = [_cycle(v, i) for i=1:n]
# if the type exists in a list, replace the first occurence. otherwise add it to the end # if the type exists in a list, replace the first occurence. otherwise add it to the end
function addOrReplace(v::AbstractVector, t::DataType, args...; kw...) function addOrReplace(v::AbstractVector, t::DataType, args...; kw...)
for (i,vi) in enumerate(v) for (i, vi) in enumerate(v)
if isa(vi, t) if isa(vi, t)
v[i] = t(args...; kw...) v[i] = t(args...; kw...)
return return
@ -254,7 +255,6 @@ end
createSegments(z) = collect(repeat(reshape(z,1,:),2,1))[2:end] createSegments(z) = collect(repeat(reshape(z,1,:),2,1))[2:end]
sortedkeys(plotattributes::Dict) = sort(collect(keys(plotattributes))) sortedkeys(plotattributes::Dict) = sort(collect(keys(plotattributes)))
function _heatmap_edges(v::AVec, isedges::Bool = false, ispolar::Bool = false) function _heatmap_edges(v::AVec, isedges::Bool = false, ispolar::Bool = false)
@ -327,20 +327,18 @@ isscalar(::Any) = false
is_2tuple(v) = typeof(v) <: Tuple && length(v) == 2 is_2tuple(v) = typeof(v) <: Tuple && length(v) == 2
isvertical(plotattributes::AKW) = get(plotattributes, :orientation, :vertical) in (:vertical, :v, :vert) isvertical(plotattributes::AKW) = get(plotattributes, :orientation, :vertical) in (:vertical, :v, :vert)
isvertical(series::Series) = isvertical(series.plotattributes) isvertical(series::Series) = isvertical(series.plotattributes)
ticksType(ticks::AVec{T}) where {T<:Real} = :ticks ticksType(ticks::AVec{T}) where {T<:Real} = :ticks
ticksType(ticks::AVec{T}) where {T<:AbstractString} = :labels ticksType(ticks::AVec{T}) where {T<:AbstractString} = :labels
ticksType(ticks::Tuple{T,S}) where {T<:Union{AVec,Tuple},S<:Union{AVec,Tuple}} = :ticks_and_labels ticksType(ticks::Tuple{T,S}) where {T<:Union{AVec,Tuple},S<:Union{AVec,Tuple}} = :ticks_and_labels
ticksType(ticks) = :invalid ticksType(ticks) = :invalid
limsType(lims::Tuple{T,S}) where {T<:Real,S<:Real} = :limits
limsType(lims::Symbol) = lims == :auto ? :auto : :invalid
limsType(lims) = :invalid
limsType(lims::Tuple{T,S}) where {T<:Real,S<:Real} = :limits
limsType(lims::Symbol) = lims == :auto ? :auto : :invalid
limsType(lims) = :invalid
# recursively merge kw-dicts, e.g. for merging extra_kwargs / extra_plot_kwargs in plotly) # recursively merge kw-dicts, e.g. for merging extra_kwargs / extra_plot_kwargs in plotly)
recursive_merge(x::AbstractDict...) = merge(recursive_merge, x...) recursive_merge(x::AbstractDict...) = merge(recursive_merge, x...)
@ -386,9 +384,7 @@ end
handle_surface(z) = z handle_surface(z) = z
handle_surface(z::Surface) = permutedims(z.surf) handle_surface(z::Surface) = permutedims(z.surf)
function ok(x::Number, y::Number, z::Number = 0) ok(x::Number, y::Number, z::Number=0) = isfinite(x) && isfinite(y) && isfinite(z)
isfinite(x) && isfinite(y) && isfinite(z)
end
ok(tup::Tuple) = ok(tup...) ok(tup::Tuple) = ok(tup...)
# compute one side of a fill range from a ribbon # compute one side of a fill range from a ribbon
@ -418,9 +414,7 @@ function concatenate_fillrange(x,y::Tuple)
return xline, yline return xline, yline
end end
function get_sp_lims(sp::Subplot, letter::Symbol) get_sp_lims(sp::Subplot, letter::Symbol) = axis_limits(sp, letter)
axis_limits(sp, letter)
end
""" """
xlims([plt]) xlims([plt])
@ -466,10 +460,7 @@ function contour_levels(series::Series, clims)
levels levels
end end
for comp in (:line, :fill, :marker) for comp in (:line, :fill, :marker)
compcolor = string(comp, :color) compcolor = string(comp, :color)
get_compcolor = Symbol(:get_, compcolor) get_compcolor = Symbol(:get_, compcolor)
comp_z = string(comp, :_z) comp_z = string(comp, :_z)
@ -478,7 +469,6 @@ for comp in (:line, :fill, :marker)
get_compalpha = Symbol(:get_, compalpha) get_compalpha = Symbol(:get_, compalpha)
@eval begin @eval begin
function $get_compcolor(series, cmin::Real, cmax::Real, i::Int = 1) function $get_compcolor(series, cmin::Real, cmax::Real, i::Int = 1)
c = series[$Symbol($compcolor)] c = series[$Symbol($compcolor)]
z = series[$Symbol($comp_z)] z = series[$Symbol($comp_z)]
@ -489,7 +479,7 @@ for comp in (:line, :fill, :marker)
end end
end end
$get_compcolor(series, clims, i::Int = 1) = $get_compcolor(series, clims[1], clims[2], i) $get_compcolor(series, clims, i::Int=1) = $get_compcolor(series, clims[1], clims[2], i)
function $get_compcolor(series, i::Int = 1) function $get_compcolor(series, i::Int = 1)
if series[$Symbol($comp_z)] === nothing if series[$Symbol($comp_z)] === nothing
@ -499,7 +489,7 @@ for comp in (:line, :fill, :marker)
end end
end end
$get_compalpha(series, i::Int = 1) = _cycle(series[$Symbol($compalpha)], i) $get_compalpha(series, i::Int=1) = _cycle(series[$Symbol($compalpha)], i)
end end
end end
@ -518,33 +508,23 @@ function get_colorgradient(series::Series)
end end
end end
single_color(c, v = 0.5) = c single_color(c, v=0.5) = c
single_color(grad::ColorGradient, v = 0.5) = grad[v] single_color(grad::ColorGradient, v=0.5) = grad[v]
get_gradient(c) = cgrad() get_gradient(c) = cgrad()
get_gradient(cg::ColorGradient) = cg get_gradient(cg::ColorGradient) = cg
get_gradient(cp::ColorPalette) = cgrad(cp, categorical = true) get_gradient(cp::ColorPalette) = cgrad(cp, categorical=true)
function get_linewidth(series, i::Int = 1) get_linewidth(series, i::Int=1) = _cycle(series[:linewidth], i)
_cycle(series[:linewidth], i) get_linestyle(series, i::Int=1) = _cycle(series[:linestyle], i)
end
function get_linestyle(series, i::Int = 1) function get_markerstrokecolor(series, i::Int=1)
_cycle(series[:linestyle], i)
end
function get_markerstrokecolor(series, i::Int = 1)
msc = series[:markerstrokecolor] msc = series[:markerstrokecolor]
isa(msc, ColorGradient) ? msc : _cycle(msc, i) isa(msc, ColorGradient) ? msc : _cycle(msc, i)
end end
function get_markerstrokealpha(series, i::Int = 1) get_markerstrokealpha(series, i::Int=1) = _cycle(series[:markerstrokealpha], i)
_cycle(series[:markerstrokealpha], i) get_markerstrokewidth(series, i::Int=1) = _cycle(series[:markerstrokewidth], i)
end
function get_markerstrokewidth(series, i::Int = 1)
_cycle(series[:markerstrokewidth], i)
end
const _segmenting_vector_attributes = ( const _segmenting_vector_attributes = (
:seriescolor, :seriescolor,
@ -564,7 +544,7 @@ const _segmenting_vector_attributes = (
:markershape, :markershape,
) )
const _segmenting_array_attributes = (:line_z, :fill_z, :marker_z) const _segmenting_array_attributes = :line_z, :fill_z, :marker_z
function has_attribute_segments(series::Series) function has_attribute_segments(series::Series)
# we want to check if a series needs to be split into segments just because # we want to check if a series needs to be split into segments just because
@ -589,19 +569,17 @@ function get_aspect_ratio(sp)
return aspect_ratio return aspect_ratio
end end
get_size(series::Series) = get_size(series.plotattributes[:subplot])
get_size(kw) = get(kw, :size, default(:size)) get_size(kw) = get(kw, :size, default(:size))
get_size(plt::Plot) = get_size(plt.attr) get_size(plt::Plot) = get_size(plt.attr)
get_size(sp::Subplot) = get_size(sp.plt) get_size(sp::Subplot) = get_size(sp.plt)
get_size(series::Series) = get_size(series.plotattributes[:subplot])
get_thickness_scaling(kw) = get(kw, :thickness_scaling, default(:thickness_scaling)) get_thickness_scaling(kw) = get(kw, :thickness_scaling, default(:thickness_scaling))
get_thickness_scaling(plt::Plot) = get_thickness_scaling(plt.attr) get_thickness_scaling(plt::Plot) = get_thickness_scaling(plt.attr)
get_thickness_scaling(sp::Subplot) = get_thickness_scaling(sp.plt) get_thickness_scaling(sp::Subplot) = get_thickness_scaling(sp.plt)
get_thickness_scaling(series::Series) = get_thickness_scaling(series::Series) = get_thickness_scaling(series.plotattributes[:subplot])
get_thickness_scaling(series.plotattributes[:subplot])
# --------------------------------------------------------------- # ---------------------------------------------------------------
makekw(; kw...) = KW(kw) makekw(; kw...) = KW(kw)
wraptuple(x::Tuple) = x wraptuple(x::Tuple) = x
@ -610,18 +588,20 @@ wraptuple(x) = (x,)
trueOrAllTrue(f::Function, x::AbstractArray) = all(f, x) trueOrAllTrue(f::Function, x::AbstractArray) = all(f, x)
trueOrAllTrue(f::Function, x) = f(x) trueOrAllTrue(f::Function, x) = f(x)
allLineTypes(arg) = trueOrAllTrue(a -> get(_typeAliases, a, a) in _allTypes, arg) allLineTypes(arg) = trueOrAllTrue(a -> get(_typeAliases, a, a) in _allTypes, arg)
allStyles(arg) = trueOrAllTrue(a -> get(_styleAliases, a, a) in _allStyles, arg) allStyles(arg) = trueOrAllTrue(a -> get(_styleAliases, a, a) in _allStyles, arg)
allShapes(arg) = trueOrAllTrue(a -> is_marker_supported(get(_markerAliases, a, a)), arg) || allShapes(arg) = (
trueOrAllTrue(a -> isa(a, Shape), arg) trueOrAllTrue(a -> is_marker_supported(get(_markerAliases, a, a)), arg) ||
allAlphas(arg) = trueOrAllTrue(a -> (typeof(a) <: Real && a > 0 && a < 1) || trueOrAllTrue(a -> isa(a, Shape), arg)
(typeof(a) <: AbstractFloat && (a == zero(typeof(a)) || a == one(typeof(a)))), arg) )
allReals(arg) = trueOrAllTrue(a -> typeof(a) <: Real, arg) allAlphas(arg) = trueOrAllTrue(
allFunctions(arg) = trueOrAllTrue(a -> isa(a, Function), arg) a -> (typeof(a) <: Real && a > 0 && a < 1) ||
(typeof(a) <: AbstractFloat && (a == zero(typeof(a)) || a == one(typeof(a)))), arg
)
allReals(arg) = trueOrAllTrue(a -> typeof(a) <: Real, arg)
allFunctions(arg) = trueOrAllTrue(a -> isa(a, Function), arg)
# --------------------------------------------------------------- # ---------------------------------------------------------------
# ---------------------------------------------------------------
""" """
Allows temporary setting of backend and defaults for Plots. Settings apply only for the `do` block. Example: Allows temporary setting of backend and defaults for Plots. Settings apply only for the `do` block. Example:
@ -655,7 +635,6 @@ function with(f::Function, args...; kw...)
oldbackend = CURRENT_BACKEND.sym oldbackend = CURRENT_BACKEND.sym
for arg in args for arg in args
# change backend? # change backend?
if arg in backends() if arg in backends()
backend(arg) backend(arg)
@ -716,10 +695,7 @@ mutable struct DebugMode
end end
const _debugMode = DebugMode(false) const _debugMode = DebugMode(false)
function debugplots(on = true) debugplots(on=true) = _debugMode.on = on
_debugMode.on = on
end
debugshow(io, x) = show(io, x) debugshow(io, x) = show(io, x)
debugshow(io, x::AbstractArray) = print(io, summary(x)) debugshow(io, x::AbstractArray) = print(io, summary(x))
@ -739,9 +715,7 @@ end
DD(io::IO, plotattributes::AKW, prefix = "") = dumpdict(io, plotattributes, prefix, true) DD(io::IO, plotattributes::AKW, prefix = "") = dumpdict(io, plotattributes, prefix, true)
DD(plotattributes::AKW, prefix = "") = DD(stdout, plotattributes, prefix) DD(plotattributes::AKW, prefix = "") = DD(stdout, plotattributes, prefix)
function dumpcallstack() dumpcallstack() = error() # well... you wanted the stacktrace, didn't you?!?
error() # well... you wanted the stacktrace, didn't you?!?
end
# ------------------------------------------------------- # -------------------------------------------------------
# NOTE: backends should implement the following methods to get/set the x/y/z data objects # NOTE: backends should implement the following methods to get/set the x/y/z data objects
@ -773,10 +747,9 @@ function setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z}
_series_updated(plt, series) _series_updated(plt, series)
end end
function setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z<:AbstractMatrix} setxyz!(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) where {X,Y,Z<:AbstractMatrix} = (
setxyz!(plt, (xyz[1], xyz[2], Surface(xyz[3])), i) setxyz!(plt, (xyz[1], xyz[2], Surface(xyz[3])), i)
end )
# ------------------------------------------------------- # -------------------------------------------------------
# indexing notation # indexing notation
@ -837,9 +810,7 @@ function extend_to_length!(v::AbstractVector, n)
extend_by_data!(v, vmax .+ (1:(n - length(v)))) extend_by_data!(v, vmax .+ (1:(n - length(v))))
end end
extend_by_data!(v::AbstractVector, x) = isimmutable(v) ? vcat(v, x) : push!(v, x) extend_by_data!(v::AbstractVector, x) = isimmutable(v) ? vcat(v, x) : push!(v, x)
function extend_by_data!(v::AbstractVector, x::AbstractVector) extend_by_data!(v::AbstractVector, x::AbstractVector) = isimmutable(v) ? vcat(v, x) : append!(v, x)
isimmutable(v) ? vcat(v, x) : append!(v, x)
end
# ------------------------------------------------------- # -------------------------------------------------------
@ -898,9 +869,7 @@ end
# push y[i] to the ith series # push y[i] to the ith series
# same x for each series # same x for each series
function Base.push!(plt::Plot, x::Real, y::AVec) Base.push!(plt::Plot, x::Real, y::AVec) = push!(plt, [x], y)
push!(plt, [x], y)
end
# push (x[i], y[i]) to the ith series # push (x[i], y[i]) to the ith series
function Base.push!(plt::Plot, x::AVec, y::AVec) function Base.push!(plt::Plot, x::AVec, y::AVec)
@ -923,37 +892,36 @@ function Base.push!(plt::Plot, x::AVec, y::AVec, z::AVec)
plt plt
end end
# --------------------------------------------------------------- # ---------------------------------------------------------------
# Some conversion functions # Some conversion functions
# note: I borrowed these conversion constants from Compose.jl's Measure # note: I borrowed these conversion constants from Compose.jl's Measure
const PX_PER_INCH = 100 const PX_PER_INCH = 100
const DPI = PX_PER_INCH const DPI = PX_PER_INCH
const MM_PER_INCH = 25.4 const MM_PER_INCH = 25.4
const MM_PER_PX = MM_PER_INCH / PX_PER_INCH const MM_PER_PX = MM_PER_INCH / PX_PER_INCH
inch2px(inches::Real) = float(inches * PX_PER_INCH) inch2px(inches::Real) = float(inches * PX_PER_INCH)
px2inch(px::Real) = float(px / PX_PER_INCH) px2inch(px::Real) = float(px / PX_PER_INCH)
inch2mm(inches::Real) = float(inches * MM_PER_INCH) inch2mm(inches::Real) = float(inches * MM_PER_INCH)
mm2inch(mm::Real) = float(mm / MM_PER_INCH) mm2inch(mm::Real) = float(mm / MM_PER_INCH)
px2mm(px::Real) = float(px * MM_PER_PX) px2mm(px::Real) = float(px * MM_PER_PX)
mm2px(mm::Real) = float(mm / MM_PER_PX) mm2px(mm::Real) = float(mm / MM_PER_PX)
"Smallest x in plot" "Smallest x in plot"
xmin(plt::Plot) = ignorenan_minimum([ignorenan_minimum(series.plotattributes[:x]) for series in plt.series_list]) xmin(plt::Plot) = ignorenan_minimum(
[ignorenan_minimum(series.plotattributes[:x]) for series in plt.series_list]
)
"Largest x in plot" "Largest x in plot"
xmax(plt::Plot) = ignorenan_maximum([ignorenan_maximum(series.plotattributes[:x]) for series in plt.series_list]) xmax(plt::Plot) = ignorenan_maximum(
[ignorenan_maximum(series.plotattributes[:x]) for series in plt.series_list]
)
"Extrema of x-values in plot" "Extrema of x-values in plot"
ignorenan_extrema(plt::Plot) = (xmin(plt), xmax(plt)) ignorenan_extrema(plt::Plot) = (xmin(plt), xmax(plt))
# --------------------------------------------------------------- # ---------------------------------------------------------------
# get fonts from objects: # get fonts from objects:
@ -1026,18 +994,18 @@ guidefont(ax::Axis) = font(;
function convert_sci_unicode(label::AbstractString) function convert_sci_unicode(label::AbstractString)
unicode_dict = Dict( unicode_dict = Dict(
'⁰' => "0", '⁰' => "0",
'¹' => "1", '¹' => "1",
'²' => "2", '²' => "2",
'³' => "3", '³' => "3",
'⁴' => "4", '⁴' => "4",
'⁵' => "5", '⁵' => "5",
'⁶' => "6", '⁶' => "6",
'⁷' => "7", '⁷' => "7",
'⁸' => "8", '⁸' => "8",
'⁹' => "9", '⁹' => "9",
'⁻' => "-", '⁻' => "-",
"×10" => "×10^{", "×10" => "×10^{",
) )
for key in keys(unicode_dict) for key in keys(unicode_dict)
label = replace(label, key => unicode_dict[key]) label = replace(label, key => unicode_dict[key])
@ -1139,40 +1107,70 @@ function shape_data(series, expansion_factor = 1)
return x, y return x, y
end end
function construct_categorical_data(x::AbstractArray, axis::Axis) construct_categorical_data(x::AbstractArray, axis::Axis) = (
map(xi -> axis[:discrete_values][searchsortedfirst(axis[:continuous_values], xi)], x) map(xi -> axis[:discrete_values][searchsortedfirst(axis[:continuous_values], xi)], x)
end )
_fmt_paragraph(paragraph::AbstractString;kwargs...) = _fmt_paragraph(IOBuffer(),paragraph,0;kwargs...) _fmt_paragraph(paragraph::AbstractString;kwargs...) = _fmt_paragraph(
IOBuffer(), paragraph, 0; kwargs...
)
function _fmt_paragraph(io::IOBuffer, function _fmt_paragraph(
remaining_text::AbstractString, io::IOBuffer, remaining_text::AbstractString, column_count::Integer;
column_count::Integer; fillwidth=60, leadingspaces=0
fillwidth=60, )
leadingspaces=0) kwargs = (fillwidth=fillwidth, leadingspaces=leadingspaces)
kwargs = (fillwidth = fillwidth, leadingspaces = leadingspaces)
m = match(r"(.*?) (.*)",remaining_text) m = match(r"(.*?) (.*)",remaining_text)
if isa(m,Nothing) if isa(m, Nothing)
if column_count + length(remaining_text) fillwidth if column_count + length(remaining_text) fillwidth
print(io,remaining_text) print(io, remaining_text)
String(take!(io)) String(take!(io))
else else
print(io,"\n"*" "^leadingspaces*remaining_text) print(io, "\n"*" "^leadingspaces*remaining_text)
String(take!(io)) String(take!(io))
end end
else else
if column_count + length(m[1]) fillwidth if column_count + length(m[1]) fillwidth
print(io,"$(m[1]) ") print(io,"$(m[1]) ")
_fmt_paragraph(io,m[2],column_count + length(m[1]) + 1;kwargs...) _fmt_paragraph(io, m[2], column_count + length(m[1]) + 1; kwargs...)
else else
print(io,"\n"*" "^leadingspaces*"$(m[1]) ") print(io,"\n"*" "^leadingspaces*"$(m[1]) ")
_fmt_paragraph(io,m[2],leadingspaces;kwargs...) _fmt_paragraph(io, m[2], leadingspaces; kwargs...)
end end
end end
end end
function _document_argument(S::AbstractString) _document_argument(S::AbstractString) = _fmt_paragraph(
_fmt_paragraph("`$S`: "*_arg_desc[Symbol(S)],leadingspaces = 6 + length(S)) "`$S`: "*_arg_desc[Symbol(S)], leadingspaces=6+length(S)
)
function mesh3d_triangles(x, y, z, cns)
if typeof(cns) <: Tuple{Array, Array, Array}
ci, cj, ck = cns
if !(length(ci) == length(cj) == length(ck))
throw(ArgumentError("Argument connections must consist of equally sized arrays."))
end
else
throw(ArgumentError("Argument connections has to be a tuple of three arrays."))
end
X = zeros(eltype(x), 4length(ci))
Y = zeros(eltype(y), 4length(cj))
Z = zeros(eltype(z), 4length(ck))
@inbounds for I 1:length(ci)
i = ci[I] + 1 # connections are 0-based
j = cj[I] + 1
k = ck[I] + 1
m = 4(I - 1) + 1; n = m + 1; o = m + 2; p = m + 3
X[m] = X[p] = x[i]
Y[m] = Y[p] = y[i]
Z[m] = Z[p] = z[i]
X[n] = x[j]
Y[n] = y[j]
Z[n] = z[j]
X[o] = x[k]
Y[o] = y[k]
Z[o] = z[k]
end
return X, Y, Z
end end