Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic plot tests #4804

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/basic_recipes/hvlines.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ function projview_to_2d_limits(plot::AbstractPlot)
xmin, xmax = minmax((((-1, 1) .- pv[1, 4]) ./ pv[1, 1])...)
ymin, ymax = minmax((((-1, 1) .- pv[2, 4]) ./ pv[2, 2])...)
origin = Vec2d(xmin, ymin)
return inv_f32_convert(f32c, Rect2d(origin, Vec2d(xmax, ymax) - origin))
r = Rect2d(origin, Vec2d(xmax, ymax) - origin)
return inv_f32_convert(f32c, r)
end
end

Expand Down
27 changes: 14 additions & 13 deletions src/float32-scaling.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ is_identity_transform(ls::Nothing) = true # Float32Convert with scaling == nothi
patch_model(plot)
patch_model(plot, f32c, model)

The (default) order of operations is:
The (default) order of operations is:

1. `plot.transformation.transform_func`
2. `plot.transformation.model`
3. `scene.float32convert`
4. `camera.projectionview`

But we want to apply the `float32convert` before `model` so that that can be
applied on the GPU. This function evaluates if this is possible and returns an
But we want to apply the `float32convert` before `model` so that that can be
applied on the GPU. This function evaluates if this is possible and returns an
adjusted `LinearScaling` Observable for `apply_transform_and_f32_conversion()`
and an adjusted `model` matrix Observable for the GPU.
"""
Expand All @@ -106,7 +106,7 @@ function patch_model(@nospecialize(plot), f32c::Float32Convert, model::Observabl
model_obs = Observable{Mat4f}(Mat4f(I), ignore_equal_values = true)

onany(plot, f32c.scaling, model, update = true) do f32c, model
# Neutral f32c can mean that data and model cancel each other and we
# Neutral f32c can mean that data and model cancel each other and we
# still have Float32 preicsion issues in between.

# works with rotation component as well, but drops signs on scale
Expand All @@ -120,17 +120,17 @@ function patch_model(@nospecialize(plot), f32c::Float32Convert, model::Observabl
model_obs[] = Mat4f(model)

elseif is_float_safe(scale, trans) && is_rot_free
# model can be applied on GPU and we can pull f32c through the
# model matrix. This can be merged with the option below, but
# model can be applied on GPU and we can pull f32c through the
# model matrix. This can be merged with the option below, but
# keeping them separate improves compatibility with transform_marker
scale = Vec3d(model[1, 1], model[2, 2], model[3, 3]) # existing scale is missing signs
f32c_obs[] = Makie.LinearScaling(
f32c.scale, ((f32c.scale .- 1) .* trans .+ f32c.offset) ./ scale
)
model_obs[] = model

elseif is_rot_free
# Model has no rotation so we can extract scale + translation and move
# Model has no rotation so we can extract scale + translation and move
# it to the f32c.
scale = Vec3d(model[1, 1], model[2, 2], model[3, 3]) # existing scale is missing signs
f32c_obs[] = Makie.LinearScaling(
Expand Down Expand Up @@ -254,6 +254,7 @@ end
@inline inv_f32_convert(c::Float32Convert, x::VecTypes{N}) where N = inv(c.scaling[])(to_ndim(Point{N, Float64}, x, 0))
@inline inv_f32_convert(c::Union{Nothing, Float32Convert}, x::AbstractArray) = inv_f32_convert.((c,), x)
@inline inv_f32_convert(ls::Float32Convert, r::Rect) = inv_f32_convert(ls.scaling[], r)
@inline inv_f32_convert(::Nothing, r::Rect) = r
@inline inv_f32_convert(x::SceneLike, args...) = inv_f32_convert(f32_conversion(x), args...)

@inline inv_f32_scale(c::Nothing, v::VecTypes{3}) = Vec3d(v)
Expand Down Expand Up @@ -322,7 +323,7 @@ function apply_transform_and_f32_conversion(
transform_func, model::Mat4d, data, space::Symbol
)
# TODO:
# - Optimization: avoid intermediate arrays
# - Optimization: avoid intermediate arrays
# - Is transform_func strictly per element?

trans, scale = decompose_translation_scale_matrix(model)
Expand All @@ -337,7 +338,7 @@ function apply_transform_and_f32_conversion(
return f32_convert(float32convert, to_ndim.(Point3d, transformed, 0), space)

else
# model contains rotation which stops us from applying f32convert
# model contains rotation which stops us from applying f32convert
# before model
transformed = apply_transform_and_model(model, transform_func, data, space)
return f32_convert(float32convert, transformed)
Expand All @@ -364,9 +365,9 @@ function apply_transform_and_f32_conversion(
float32convert::Union{Nothing, Float32Convert, LinearScaling},
transform_func, model::Mat4d, data, dim::Integer, space::Symbol
)

dim in (1, 2, 3) || error("The transform_func and float32 conversion can only be applied along dimensions 1, 2 or 3, not $dim")

dimpoints = if dim == 1
Point2.(data, 0)
elseif dim == 2
Expand All @@ -380,7 +381,7 @@ function apply_transform_and_f32_conversion(
# model applied on GPU, float32convert skippable
transformed = apply_transform(transform_func, dimpoints, space)
return [Float32(p[dim]) for p in transformed]

elseif is_translation_scale_matrix(model)
# translation and scale of model have been moved to f32convert, so just apply that
transformed = apply_transform(transform_func, dimpoints, space)
Expand Down
9 changes: 9 additions & 0 deletions test/plots/generic.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@testset "plot!() to scene" begin
scene = Scene()
for PlotType in ALL_PLOT_TYPES
@testset "$PlotType" begin
testplot!(scene, PlotType)
@test true
end
end
end
3 changes: 3 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ using Makie.IntervalSets
using GeometryBasics: Pyramid

using Makie: volume

include("testplot.jl")
# COV_EXCL_STOP

@testset "Unit tests" begin
Expand All @@ -27,6 +29,7 @@ using Makie: volume

@testset "Plots" begin
include("plots/primitives.jl")
include("plots/generic.jl")
include("plots/text.jl")
include("plots/barplot.jl")
include("plots/hist.jl")
Expand Down
102 changes: 102 additions & 0 deletions test/testplot.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using SparseArrays
using Random

const ALL_PLOT_PRIMITIVES = [
Scatter, Lines, LineSegments, Makie.Text, Mesh, MeshScatter, Image, Heatmap,
Surface, Volume, Voxels
]
const ALL_PLOT_TYPES = vcat(ALL_PLOT_PRIMITIVES, [
# skipped Annotations, Axis3D
ABLines, Arc, Arrows, Band, Makie.BarPlot, Bracket, Contourf, Contour, Contour3d,
DataShader, Errorbars, Rangebars, HLines, VLines, HSpan, VSpan, Pie, Poly,
RainClouds, ScatterLines, Series, Spy, Stairs, Stem, StreamPlot, TimeSeries,
Tooltip, Tricontourf, Triplot, VolumeSlices, Voronoiplot, Waterfall, Wireframe,
BoxPlot, CrossBar, Density, QQPlot, QQNorm, ECDFPlot, Hexbin, Hist, Violin
])

"""
testplot!(scenelike, PlotType[; kwargs...])

Creates a sample plot of type `PlotType` with random data in the given `scenelike`.
Any keyword arguments are passed to the plot function.

This is meant to simplify creating throw-away plots for testing attributes.
`ALL_PLOT_TYPES` may also be useful.
"""
function testplot!(scene, ::Type{PlotType}, kwargs...) where {PlotType <: Plot}
CT = Makie.conversion_trait(PlotType)
f = Makie.MakieCore.plotfunc!(PlotType)

if CT === PointBased() || PlotType <: Union{Makie.Text, ABLines, BarPlot,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this multiple dispatch based, maybe so it returns aplotspec that can be decomposed to args and kwargs and then plotted?

That way it's easy to extend as we get more recipes, or for external recipes we may want to test.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I guess multiple dispatch would be better if another library wants to reuse and extend the Code for testing. If it's just used here I don't think multiple dispatch matters much.

I'm not sure if PlotSpec is a good idea. It's calling to_value on kwargs/attributes and I'd assume it circumvents parts of the normal plot pipeline. It should probably be tested separately

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, maybe just returning a tuple (; args, kwargs) might work?

HSpan, VSpan, Triplot, Voronoiplot, BoxPlot, QQPlot, QQNorm}
return f(scene, rand(10), rand(10); kwargs...)

elseif CT isa Union{Makie.GridBased, Makie.ImageLike}
return f(scene, rand(10, 10); kwargs...)

elseif CT isa Makie.VolumeLike || PlotType <: Voxels
return f(scene, rand(10, 10, 10); kwargs...)

elseif PlotType <: Violin # TODO: doesn't work with SampleBased() input
return f(scene, rand(1:3, 100), rand(100); kwargs...)

elseif CT isa Makie.SampleBased || PlotType <: Union{Density, ECDFPlot, Hist}
return f(scene, rand(100); kwargs...)

elseif PlotType <: Union{HLines, VLines, Pie}
return f(scene, rand(10); kwargs...)

elseif PlotType <: Union{Band, Errorbars, Rangebars, Tricontourf}
return f(scene, 1:10, rand(10), 1 .+ rand(10); kwargs...)

elseif PlotType <: Union{Mesh, Poly, Wireframe}
return f(scene, Rect2f(rand(Point2f), rand(Vec2f)); kwargs...)

elseif PlotType <: Arc
return f(scene, rand(Point2f), rand(), minmax(rand(), rand())...; kwargs...)

elseif PlotType <: Arrows
return f(scene, rand(Point2f, 10), rand(Vec2f, 10); kwargs...)

elseif PlotType <: Bracket
return f(scene, rand(Point2f), rand(Point2f); kwargs...)

elseif PlotType <: DataShader
return f(scene, rand(Point2f, 100); kwargs...)

elseif PlotType <: RainClouds
return f(scene, rand(["A", "B", "C"], 100), rand(100); kwargs...)

elseif PlotType <: Series # TODO: merge with GridBased, ImageLike once more than 7 categories work
return f(scene, [rand(Point2f, 10) for _ in 1:3]; kwargs...)

elseif PlotType <: Spy
return f(scene, sparse(rand(10, 10) .< 0.5); kwargs...)

elseif PlotType <: StreamPlot
return f(scene, (x, y) -> rand(Point2f), -1..1, -1..1; kwargs...)

elseif PlotType <: TimeSeries
obs = Observable(rand())
p = f(scene, obs; kwargs...)
for _ in 1:10
sleep(0.01)
obs[] = rand()
end
return p

elseif PlotType <: Tooltip
return f(scene, rand(Point2f), randstring(20); kwargs...)

elseif PlotType <: VolumeSlices
return f(scene, 1:10, 1:10, 1:10, rand(10,10,10); kwargs...)

elseif PlotType <: CrossBar
return f(scene, 1:10, 0 .- rand(10), rand(10), 1 .+ rand(10); kwargs...)

else
error("$PlotType not recognized")
end
return
end

Loading