Plotly: Enable specified contour values for ranges; warn otherwise (#3757)
This commit is contained in:
parent
0742d475bc
commit
52ec432cfa
@ -30,7 +30,7 @@ const _arg_desc = KW(
|
|||||||
:marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.",
|
:marker_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series data point, which correspond to the color to be used from a markercolor gradient.",
|
||||||
:line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).",
|
:line_z => "AbstractVector, Function `f(x,y,z) -> z_value`, or Function `f(x,y) -> z_value`, or nothing. z-values for each series line segment, which correspond to the color to be used from a linecolor gradient. Note that for N points, only the first N-1 values are used (one per line-segment).",
|
||||||
:fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.",
|
:fill_z => "Matrix{Float64} of the same size as z matrix, which specifies the color of the 3D surface; the default value is `nothing`.",
|
||||||
:levels => "Integer, NTuple{2,Integer}, or AbstractVector. Levels or number of levels (or x-levels/y-levels) for a contour type.",
|
:levels => "Integer (number of contours) or AbstractVector (contour values). Determines contour levels for a contour type.",
|
||||||
:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).",
|
:orientation => "Symbol. Horizontal or vertical orientation for bar types. Values `:h`, `:hor`, `:horizontal` correspond to horizontal (sideways, anchored to y-axis), and `:v`, `:vert`, and `:vertical` correspond to vertical (the default).",
|
||||||
:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)",
|
:bar_position => "Symbol. Choose from `:overlay` (default), `:stack`. (warning: May not be implemented fully)",
|
||||||
:bar_width => "nothing or Number. Width of bars in data coordinates. When nothing, chooses based on x (or y when `orientation = :h`).",
|
:bar_width => "nothing or Number. Width of bars in data coordinates. When nothing, chooses based on x (or y when `orientation = :h`).",
|
||||||
|
|||||||
25
src/args.jl
25
src/args.jl
@ -1502,6 +1502,11 @@ function RecipesPipeline.preprocess_attributes!(plotattributes::AKW)
|
|||||||
plotattributes[:framestyle] = _framestyleAliases[plotattributes[:framestyle]]
|
plotattributes[:framestyle] = _framestyleAliases[plotattributes[:framestyle]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# contours
|
||||||
|
if haskey(plotattributes, :levels)
|
||||||
|
check_contour_levels(plotattributes[:levels])
|
||||||
|
end
|
||||||
|
|
||||||
# warnings for moved recipes
|
# warnings for moved recipes
|
||||||
st = get(plotattributes, :seriestype, :path)
|
st = get(plotattributes, :seriestype, :path)
|
||||||
if st in (:boxplot, :violin, :density) && !isdefined(Main, :StatsPlots)
|
if st in (:boxplot, :violin, :density) && !isdefined(Main, :StatsPlots)
|
||||||
@ -1629,6 +1634,26 @@ convertLegendValue(v::AbstractArray) = map(convertLegendValue, v)
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
"""Throw an error if the `levels` keyword argument is not of the correct type
|
||||||
|
or `levels` is less than 1"""
|
||||||
|
function check_contour_levels(levels)
|
||||||
|
if !(levels isa Union{Integer,AVec})
|
||||||
|
throw(
|
||||||
|
ArgumentError(
|
||||||
|
"the levels keyword argument must be an integer or AbstractVector",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
elseif levels isa Integer && levels <= 0
|
||||||
|
throw(
|
||||||
|
ArgumentError(
|
||||||
|
"must pass a positive number of contours to the levels keyword argument",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
# 1-row matrices will give an element
|
# 1-row matrices will give an element
|
||||||
# multi-row matrices will give a column
|
# multi-row matrices will give a column
|
||||||
# InputWrapper just gives the contents
|
# InputWrapper just gives the contents
|
||||||
|
|||||||
@ -617,11 +617,34 @@ function plotly_series(plt::Plot, series::Series)
|
|||||||
filled = isfilledcontour(series)
|
filled = isfilledcontour(series)
|
||||||
plotattributes_out[:type] = "contour"
|
plotattributes_out[:type] = "contour"
|
||||||
plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z
|
plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z
|
||||||
plotattributes_out[:ncontours] = series[:levels] + 2
|
|
||||||
plotattributes_out[:contours] = KW(
|
plotattributes_out[:contours] = KW(
|
||||||
:coloring => filled ? "fill" : "lines",
|
:coloring => filled ? "fill" : "lines",
|
||||||
:showlabels => series[:contour_labels] == true,
|
:showlabels => series[:contour_labels] == true,
|
||||||
)
|
)
|
||||||
|
# Plotly does not support arbitrary sets of contours
|
||||||
|
# (https://github.com/plotly/plotly.js/issues/4503)
|
||||||
|
# so we distinguish AbstractRanges and AbstractVectors
|
||||||
|
let levels = series[:levels]
|
||||||
|
if levels isa AbstractRange
|
||||||
|
plotattributes_out[:contours][:start] = first(levels)
|
||||||
|
plotattributes_out[:contours][:end] = last(levels)
|
||||||
|
plotattributes_out[:contours][:size] = step(levels)
|
||||||
|
elseif levels isa AVec
|
||||||
|
levels_range =
|
||||||
|
range(first(levels), stop = last(levels), length = length(levels))
|
||||||
|
plotattributes_out[:contours][:start] = first(levels_range)
|
||||||
|
plotattributes_out[:contours][:end] = last(levels_range)
|
||||||
|
plotattributes_out[:contours][:size] = step(levels_range)
|
||||||
|
@warn(
|
||||||
|
"setting arbitrary contour levels with Plotly backend is not supported; " *
|
||||||
|
"use a range to set equally-spaced contours or an integer to set the " *
|
||||||
|
"approximate number of contours with the keyword `levels`. " *
|
||||||
|
"Setting levels to $(levels_range)"
|
||||||
|
)
|
||||||
|
elseif levels isa Integer
|
||||||
|
plotattributes_out[:ncontours] = levels + 2
|
||||||
|
end
|
||||||
|
end
|
||||||
plotattributes_out[:colorscale] =
|
plotattributes_out[:colorscale] =
|
||||||
plotly_colorscale(series[:linecolor], series[:linealpha])
|
plotly_colorscale(series[:linecolor], series[:linealpha])
|
||||||
plotattributes_out[:showscale] = hascolorbar(sp) && hascolorbar(series)
|
plotattributes_out[:showscale] = hascolorbar(sp) && hascolorbar(series)
|
||||||
|
|||||||
@ -390,8 +390,6 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
|||||||
elseif isvector(levels)
|
elseif isvector(levels)
|
||||||
extrakw[:levels] = levels
|
extrakw[:levels] = levels
|
||||||
()
|
()
|
||||||
else
|
|
||||||
error("Only numbers and vectors are supported with levels keyword")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# add custom frame shapes to markershape?
|
# add custom frame shapes to markershape?
|
||||||
|
|||||||
@ -31,6 +31,7 @@ end # testset
|
|||||||
include("test_defaults.jl")
|
include("test_defaults.jl")
|
||||||
include("test_pipeline.jl")
|
include("test_pipeline.jl")
|
||||||
include("test_axes.jl")
|
include("test_axes.jl")
|
||||||
|
include("test_contours.jl")
|
||||||
include("test_axis_letter.jl")
|
include("test_axis_letter.jl")
|
||||||
include("test_components.jl")
|
include("test_components.jl")
|
||||||
include("test_shorthands.jl")
|
include("test_shorthands.jl")
|
||||||
@ -38,6 +39,7 @@ include("integration_dates.jl")
|
|||||||
include("test_recipes.jl")
|
include("test_recipes.jl")
|
||||||
include("test_hdf5plots.jl")
|
include("test_hdf5plots.jl")
|
||||||
include("test_pgfplotsx.jl")
|
include("test_pgfplotsx.jl")
|
||||||
|
include("test_plotly.jl")
|
||||||
|
|
||||||
reference_dir(args...) =
|
reference_dir(args...) =
|
||||||
joinpath(homedir(), ".julia", "dev", "PlotReferenceImages", args...)
|
joinpath(homedir(), ".julia", "dev", "PlotReferenceImages", args...)
|
||||||
|
|||||||
68
test/test_contours.jl
Normal file
68
test/test_contours.jl
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using Plots, Test
|
||||||
|
import RecipesPipeline
|
||||||
|
|
||||||
|
@testset "Contours" begin
|
||||||
|
@testset "check_contour_levels" begin
|
||||||
|
@test Plots.check_contour_levels(2) === nothing
|
||||||
|
@test Plots.check_contour_levels(-1.0:0.2:10.0) === nothing
|
||||||
|
@test Plots.check_contour_levels([-100, -2, -1, 0, 1, 2, 100]) === nothing
|
||||||
|
@test_throws ArgumentError Plots.check_contour_levels(1.0)
|
||||||
|
@test_throws ArgumentError Plots.check_contour_levels((1, 2, 3))
|
||||||
|
@test_throws ArgumentError Plots.check_contour_levels(-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "RecipesPipeline.preprocess_attributes!" begin
|
||||||
|
function equal_after_pipeline(kw)
|
||||||
|
kw′ = deepcopy(kw)
|
||||||
|
RecipesPipeline.preprocess_attributes!(kw′)
|
||||||
|
kw == kw′
|
||||||
|
end
|
||||||
|
|
||||||
|
@test equal_after_pipeline(KW(:levels => 1))
|
||||||
|
@test equal_after_pipeline(KW(:levels => 1:10))
|
||||||
|
@test equal_after_pipeline(KW(:levels => [1.0, 3.0, 5.0]))
|
||||||
|
@test_throws ArgumentError RecipesPipeline.preprocess_attributes!(
|
||||||
|
KW(:levels => 1.0),
|
||||||
|
)
|
||||||
|
@test_throws ArgumentError RecipesPipeline.preprocess_attributes!(
|
||||||
|
KW(:levels => (1, 2, 3)),
|
||||||
|
)
|
||||||
|
@test_throws ArgumentError RecipesPipeline.preprocess_attributes!(KW(:levels => -3))
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "contour[f]" begin
|
||||||
|
x = (-2π):0.1:(2π)
|
||||||
|
y = (-π):0.1:π
|
||||||
|
z = cos.(y) .* sin.(x')
|
||||||
|
|
||||||
|
@testset "Incorrect input" begin
|
||||||
|
@test_throws ArgumentError contour(x, y, z, levels = 1.0)
|
||||||
|
@test_throws ArgumentError contour(x, y, z, levels = (1, 2, 3))
|
||||||
|
@test_throws ArgumentError contour(x, y, z, levels = -3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Default number" begin
|
||||||
|
@test contour(x, y, z)[1][1].plotattributes[:levels] ==
|
||||||
|
Plots._series_defaults[:levels]
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Number" begin
|
||||||
|
@testset "$n contours" for n in (2, 5, 100)
|
||||||
|
p = contour(x, y, z, levels = n)
|
||||||
|
attr = p[1][1].plotattributes
|
||||||
|
@test attr[:seriestype] == :contour
|
||||||
|
@test attr[:levels] == n
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Range" begin
|
||||||
|
levels = -1:0.5:1
|
||||||
|
@test contour(x, y, z, levels = levels)[1][1].plotattributes[:levels] == levels
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Set of levels" begin
|
||||||
|
levels = [-1, 0.25, 0, 0.25, 1]
|
||||||
|
@test contour(x, y, z, levels = levels)[1][1].plotattributes[:levels] == levels
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
57
test/test_plotly.jl
Normal file
57
test/test_plotly.jl
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using Plots, Test
|
||||||
|
|
||||||
|
@testset "Plotly" begin
|
||||||
|
@testset "Basic" begin
|
||||||
|
@test plotly() == Plots.PlotlyBackend()
|
||||||
|
@test backend() == Plots.PlotlyBackend()
|
||||||
|
|
||||||
|
p = plot(rand(10))
|
||||||
|
@test isa(p, Plots.Plot) == true
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Contours" begin
|
||||||
|
x = (-2π):0.1:(2π)
|
||||||
|
y = (-π):0.1:π
|
||||||
|
z = cos.(y) .* sin.(x')
|
||||||
|
|
||||||
|
@testset "Contour numbers" begin
|
||||||
|
@testset "Default" begin
|
||||||
|
@test Plots.plotly_series(contour(x, y, z))[1][:ncontours] ==
|
||||||
|
Plots._series_defaults[:levels] + 2
|
||||||
|
end
|
||||||
|
@testset "Specified number" begin
|
||||||
|
@test Plots.plotly_series(contour(x, y, z, levels = 10))[1][:ncontours] ==
|
||||||
|
12
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Contour values" begin
|
||||||
|
@testset "Range" begin
|
||||||
|
levels = -1:0.5:1
|
||||||
|
p = contour(x, y, z, levels = levels)
|
||||||
|
@test p[1][1].plotattributes[:levels] == levels
|
||||||
|
@test Plots.plotly_series(p)[1][:contours][:start] == first(levels)
|
||||||
|
@test Plots.plotly_series(p)[1][:contours][:end] == last(levels)
|
||||||
|
@test Plots.plotly_series(p)[1][:contours][:size] == step(levels)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Set of contours" begin
|
||||||
|
levels = [-1, -0.25, 0, 0.25, 1]
|
||||||
|
levels_range =
|
||||||
|
range(first(levels), stop = last(levels), length = length(levels))
|
||||||
|
p = contour(x, y, z, levels = levels)
|
||||||
|
@test p[1][1].plotattributes[:levels] == levels
|
||||||
|
series_dict = @test_logs (
|
||||||
|
:warn,
|
||||||
|
"setting arbitrary contour levels with Plotly backend " *
|
||||||
|
"is not supported; use a range to set equally-spaced contours or an " *
|
||||||
|
"integer to set the approximate number of contours with the keyword " *
|
||||||
|
"`levels`. Setting levels to -1.0:0.5:1.0",
|
||||||
|
) Plots.plotly_series(p)
|
||||||
|
@test series_dict[1][:contours][:start] == first(levels_range)
|
||||||
|
@test series_dict[1][:contours][:end] == last(levels_range)
|
||||||
|
@test series_dict[1][:contours][:size] == step(levels_range)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user