Add :mesh3d seriesstyle for PyPlot backend (#3835)
* Add `:mesh3d` seriesstyle for PyPlot backend via `Poly3DCollection`. * Add `:connections` to lists of supported keywords for the backends which support `:mesh3d` as series type. * Remove #47 from list of skipping examples for PyPlot * Add support for only-triangle syntax for `:connections` kw for PyPlot * Add 1-based indexing syntax for `connections` kw in `:mesh3d` * Update description for example `#47` (`:mesh3d`) * Hotfix 1-based indexing for mesh3d in pgfplotsx
This commit is contained in:
parent
4d40bae9cc
commit
866cb0c335
@ -402,6 +402,7 @@ const _gr_attr = merge_with_base_supported([
|
|||||||
:tick_direction,
|
:tick_direction,
|
||||||
:camera,
|
:camera,
|
||||||
:contour_labels,
|
:contour_labels,
|
||||||
|
:connections,
|
||||||
])
|
])
|
||||||
const _gr_seriestype = [
|
const _gr_seriestype = [
|
||||||
:path,
|
:path,
|
||||||
@ -521,6 +522,7 @@ const _plotly_attr = merge_with_base_supported([
|
|||||||
:tick_direction,
|
:tick_direction,
|
||||||
:camera,
|
:camera,
|
||||||
:contour_labels,
|
:contour_labels,
|
||||||
|
:connections,
|
||||||
])
|
])
|
||||||
|
|
||||||
const _plotly_seriestype = [
|
const _plotly_seriestype = [
|
||||||
@ -777,6 +779,7 @@ const _pyplot_attr = merge_with_base_supported([
|
|||||||
:tick_direction,
|
:tick_direction,
|
||||||
:camera,
|
:camera,
|
||||||
:contour_labels,
|
:contour_labels,
|
||||||
|
:connections,
|
||||||
])
|
])
|
||||||
const _pyplot_seriestype = [
|
const _pyplot_seriestype = [
|
||||||
:path,
|
:path,
|
||||||
@ -793,6 +796,7 @@ const _pyplot_seriestype = [
|
|||||||
:contour3d,
|
:contour3d,
|
||||||
:path3d,
|
:path3d,
|
||||||
:scatter3d,
|
:scatter3d,
|
||||||
|
:mesh3d,
|
||||||
:surface,
|
:surface,
|
||||||
:wireframe,
|
:wireframe,
|
||||||
]
|
]
|
||||||
@ -860,6 +864,7 @@ const _gaston_attr = merge_with_base_supported([
|
|||||||
# :framestyle,
|
# :framestyle,
|
||||||
# :camera,
|
# :camera,
|
||||||
# :contour_labels,
|
# :contour_labels,
|
||||||
|
:connections,
|
||||||
])
|
])
|
||||||
|
|
||||||
const _gaston_seriestype = [
|
const _gaston_seriestype = [
|
||||||
@ -1240,6 +1245,7 @@ const _pgfplotsx_attr = merge_with_base_supported([
|
|||||||
:tick_direction,
|
:tick_direction,
|
||||||
:camera,
|
:camera,
|
||||||
:contour_labels,
|
:contour_labels,
|
||||||
|
:connections,
|
||||||
])
|
])
|
||||||
const _pgfplotsx_seriestype = [
|
const _pgfplotsx_seriestype = [
|
||||||
:path,
|
:path,
|
||||||
|
|||||||
@ -512,10 +512,26 @@ function pgfx_add_series!(::Val{:heatmap}, axis, series_opt, series, series_func
|
|||||||
end
|
end
|
||||||
|
|
||||||
function pgfx_add_series!(::Val{:mesh3d}, axis, series_opt, series, series_func, opt)
|
function pgfx_add_series!(::Val{:mesh3d}, axis, series_opt, series, series_func, opt)
|
||||||
ptable = join(
|
if opt[:connections] isa Tuple{Array,Array,Array}
|
||||||
[string(i, " ", j, " ", k, "\\\\") for (i, j, k) in zip(opt[:connections]...)],
|
# 0-based indexing
|
||||||
"\n ",
|
ptable = join(
|
||||||
)
|
[string(i, " ", j, " ", k, "\\\\") for (i, j, k) in zip(opt[:connections]...)],
|
||||||
|
"\n ",
|
||||||
|
)
|
||||||
|
elseif typeof(opt[:connections]) <: AbstractVector{NTuple{3, Int}}
|
||||||
|
# 1-based indexing
|
||||||
|
ptable = join(
|
||||||
|
[string(i-1, " ", j-1, " ", k-1, "\\\\") for (i, j, k) in opt[:connections]],
|
||||||
|
"\n ",
|
||||||
|
)
|
||||||
|
else
|
||||||
|
throw(
|
||||||
|
ArgumentError(
|
||||||
|
"Argument connections has to be either a tuple of three arrays (0-based indexing)
|
||||||
|
or an AbstractVector{NTuple{3,Int}} (1-based indexing).",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
end
|
||||||
push!(
|
push!(
|
||||||
series_opt,
|
series_opt,
|
||||||
"patch" => nothing,
|
"patch" => nothing,
|
||||||
|
|||||||
@ -678,6 +678,7 @@ function plotly_series(plt::Plot, series::Series)
|
|||||||
|
|
||||||
if series[:connections] !== nothing
|
if series[:connections] !== nothing
|
||||||
if typeof(series[:connections]) <: Tuple{Array,Array,Array}
|
if typeof(series[:connections]) <: Tuple{Array,Array,Array}
|
||||||
|
# 0-based indexing
|
||||||
i, j, k = series[:connections]
|
i, j, k = series[:connections]
|
||||||
if !(length(i) == length(j) == length(k))
|
if !(length(i) == length(j) == length(k))
|
||||||
throw(
|
throw(
|
||||||
@ -689,10 +690,17 @@ function plotly_series(plt::Plot, series::Series)
|
|||||||
plotattributes_out[:i] = i
|
plotattributes_out[:i] = i
|
||||||
plotattributes_out[:j] = j
|
plotattributes_out[:j] = j
|
||||||
plotattributes_out[:k] = k
|
plotattributes_out[:k] = k
|
||||||
|
elseif typeof(series[:connections]) <: AbstractVector{NTuple{3, Int}}
|
||||||
|
# 1-based indexing
|
||||||
|
i, j, k = broadcast(i -> [ inds[i]-1 for inds in series[:connections]], (1, 2, 3))
|
||||||
|
plotattributes_out[:i] = i
|
||||||
|
plotattributes_out[:j] = j
|
||||||
|
plotattributes_out[:k] = k
|
||||||
else
|
else
|
||||||
throw(
|
throw(
|
||||||
ArgumentError(
|
ArgumentError(
|
||||||
"Argument connections has to be a tuple of three arrays.",
|
"Argument connections has to be either a tuple of three arrays (0-based indexing)
|
||||||
|
or an AbstractVector{NTuple{3,Int}} (1-based indexing).",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -698,6 +698,43 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if st == :mesh3d
|
||||||
|
polygons = if series[:connections] isa AbstractVector{<:AbstractVector{Int}}
|
||||||
|
# Combination of any polygon types
|
||||||
|
broadcast(inds -> broadcast(i -> [x[i], y[i], z[i]], inds), series[:connections])
|
||||||
|
elseif series[:connections] isa AbstractVector{NTuple{N, Int}} where N
|
||||||
|
# Only N-gons - connections have to be 1-based (indexing)
|
||||||
|
broadcast(inds -> broadcast(i -> [x[i], y[i], z[i]], inds), series[:connections])
|
||||||
|
elseif series[:connections] isa NTuple{3,<:AbstractVector{Int}}
|
||||||
|
# Only triangles - connections have to be 0-based (indexing)
|
||||||
|
ci, cj, ck = series[:connections]
|
||||||
|
if !(length(ci) == length(cj) == length(ck))
|
||||||
|
throw(
|
||||||
|
ArgumentError("Argument connections must consist of equally sized arrays."),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
broadcast(j -> broadcast(i -> [x[i], y[i], z[i]], [ci[j]+1, cj[j]+1, ck[j]+1]), eachindex(ci))
|
||||||
|
else
|
||||||
|
throw(
|
||||||
|
ArgumentError("Unsupported `:connections` type $(typeof(series[:connections])) for seriestype=$st"),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
col = mplot3d.art3d.Poly3DCollection(polygons,
|
||||||
|
linewidths = py_thickness_scale(plt, series[:linewidth]),
|
||||||
|
edgecolor = py_color(get_linecolor(series)),
|
||||||
|
facecolor = py_color(series[:fillcolor]),
|
||||||
|
alpha = get_fillalpha(series),
|
||||||
|
zorder = series[:series_plotindex]
|
||||||
|
)
|
||||||
|
handle = ax."add_collection3d"(col)
|
||||||
|
# Fix for handle: https://stackoverflow.com/questions/54994600/pyplot-legend-poly3dcollection-object-has-no-attribute-edgecolors2d
|
||||||
|
# It seems there aren't two different alpha values for edge and face
|
||||||
|
handle._facecolors2d = py_color(series[:fillcolor])
|
||||||
|
handle._edgecolors2d = py_color(get_linecolor(series))
|
||||||
|
push!(handles, handle)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
if st == :image
|
if st == :image
|
||||||
xmin, xmax = ignorenan_extrema(series[:x])
|
xmin, xmax = ignorenan_extrema(series[:x])
|
||||||
ymin, ymax = ignorenan_extrema(series[:y])
|
ymin, ymax = ignorenan_extrema(series[:y])
|
||||||
|
|||||||
@ -962,8 +962,10 @@ const _examples = PlotExample[
|
|||||||
"""
|
"""
|
||||||
Allows to plot arbitrary 3d meshes. If only x,y,z are given the mesh is generated automatically.
|
Allows to plot arbitrary 3d meshes. If only x,y,z are given the mesh is generated automatically.
|
||||||
You can also specify the connections using the connections keyword.
|
You can also specify the connections using the connections keyword.
|
||||||
The connections are specified using a tuple of vectors. Each vector contains the 0-based indices of one point of a triangle,
|
The connections can be specified in two ways: Either as a tuple of vectors where each vector
|
||||||
such that elements at the same position of these vectors form a triangle.
|
contains the 0-based indices of one point of a triangle, such that elements at the same
|
||||||
|
position of these vectors form a triangle. Or as a vector of NTuple{3,Ints} where each element
|
||||||
|
contains the 1-based indices of the three points of a triangle.
|
||||||
""",
|
""",
|
||||||
[
|
[
|
||||||
:(
|
:(
|
||||||
@ -979,13 +981,14 @@ const _examples = PlotExample[
|
|||||||
i = [0, 0, 0, 1]
|
i = [0, 0, 0, 1]
|
||||||
j = [1, 2, 3, 2]
|
j = [1, 2, 3, 2]
|
||||||
k = [2, 3, 1, 3]
|
k = [2, 3, 1, 3]
|
||||||
|
# Or: cns = [(1, 2, 3), (1, 3, 4), (1, 4, 2), (2, 3, 4)] (1-based indexing)
|
||||||
|
|
||||||
# the four triangles gives above give a tetrahedron
|
# the four triangles gives above give a tetrahedron
|
||||||
mesh3d(
|
mesh3d(
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
z;
|
z;
|
||||||
connections = (i, j, k),
|
connections = (i, j, k), # connections = cns
|
||||||
title = "triangles",
|
title = "triangles",
|
||||||
xlabel = "x",
|
xlabel = "x",
|
||||||
ylabel = "y",
|
ylabel = "y",
|
||||||
@ -1235,7 +1238,7 @@ const _examples = PlotExample[
|
|||||||
_animation_examples = [2, 31]
|
_animation_examples = [2, 31]
|
||||||
_backend_skips = Dict(
|
_backend_skips = Dict(
|
||||||
:gr => [25, 30],
|
:gr => [25, 30],
|
||||||
:pyplot => [2, 25, 30, 31, 47, 49, 55],
|
:pyplot => [2, 25, 30, 31, 49, 55],
|
||||||
:plotlyjs => [2, 21, 24, 25, 30, 31, 49, 51, 55],
|
:plotlyjs => [2, 21, 24, 25, 30, 31, 49, 51, 55],
|
||||||
:plotly => [2, 21, 24, 25, 30, 31, 49, 50, 51, 55],
|
:plotly => [2, 21, 24, 25, 30, 31, 49, 50, 51, 55],
|
||||||
:pgfplotsx => [
|
:pgfplotsx => [
|
||||||
|
|||||||
40
src/utils.jl
40
src/utils.jl
@ -1181,16 +1181,12 @@ end
|
|||||||
_document_argument(S::AbstractString) =
|
_document_argument(S::AbstractString) =
|
||||||
_fmt_paragraph("`$S`: " * _arg_desc[Symbol(S)], leadingspaces = 6 + length(S))
|
_fmt_paragraph("`$S`: " * _arg_desc[Symbol(S)], leadingspaces = 6 + length(S))
|
||||||
|
|
||||||
function mesh3d_triangles(x, y, z, cns)
|
function mesh3d_triangles(x, y, z, cns::Tuple{Array,Array,Array})
|
||||||
if typeof(cns) <: Tuple{Array,Array,Array}
|
ci, cj, ck = cns
|
||||||
ci, cj, ck = cns
|
if !(length(ci) == length(cj) == length(ck))
|
||||||
if !(length(ci) == length(cj) == length(ck))
|
throw(
|
||||||
throw(
|
ArgumentError("Argument connections must consist of equally sized arrays."),
|
||||||
ArgumentError("Argument connections must consist of equally sized arrays."),
|
)
|
||||||
)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
throw(ArgumentError("Argument connections has to be a tuple of three arrays."))
|
|
||||||
end
|
end
|
||||||
X = zeros(eltype(x), 4length(ci))
|
X = zeros(eltype(x), 4length(ci))
|
||||||
Y = zeros(eltype(y), 4length(cj))
|
Y = zeros(eltype(y), 4length(cj))
|
||||||
@ -1215,6 +1211,30 @@ function mesh3d_triangles(x, y, z, cns)
|
|||||||
end
|
end
|
||||||
return X, Y, Z
|
return X, Y, Z
|
||||||
end
|
end
|
||||||
|
function mesh3d_triangles(x, y, z, cns::AbstractVector{NTuple{3, Int}})
|
||||||
|
X = zeros(eltype(x), 4length(cns))
|
||||||
|
Y = zeros(eltype(y), 4length(cns))
|
||||||
|
Z = zeros(eltype(z), 4length(cns))
|
||||||
|
@inbounds for I in 1:length(cns)
|
||||||
|
i = cns[I][1] # connections are 1-based
|
||||||
|
j = cns[I][2]
|
||||||
|
k = cns[I][3]
|
||||||
|
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
|
||||||
|
|
||||||
# cache joined symbols so they can be looked up instead of constructed each time
|
# cache joined symbols so they can be looked up instead of constructed each time
|
||||||
const _attrsymbolcache = Dict{Symbol,Dict{Symbol,Symbol}}()
|
const _attrsymbolcache = Dict{Symbol,Dict{Symbol,Symbol}}()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user