diff --git a/GraphRecipes/src/GraphRecipes.jl b/GraphRecipes/src/GraphRecipes.jl index 9147c81cc..d62ba85a0 100644 --- a/GraphRecipes/src/GraphRecipes.jl +++ b/GraphRecipes/src/GraphRecipes.jl @@ -3,6 +3,7 @@ module GraphRecipes using Graphs using PlotUtils # ColorGradient using RecipesBase +using PlotsBase: PlotsBase, partialcircle using InteractiveUtils # subtypes using LinearAlgebra diff --git a/GraphRecipes/src/graphs.jl b/GraphRecipes/src/graphs.jl index 2f1d2f1a5..65a2576c6 100644 --- a/GraphRecipes/src/graphs.jl +++ b/GraphRecipes/src/graphs.jl @@ -1132,7 +1132,7 @@ more details. colorbar_entry --> false markersize := 0 markeralpha := 0 - markerstrokesize := 0 + makerstrokewidth := 0 isnothing(edgelabel) || (annotations --> edge_label_array) else seriestype := :scatter @@ -1153,7 +1153,7 @@ more details. colorbar_entry --> false markersize := 0 markeralpha := 0 - markerstrokesize := 0 + makerstrokewidth := 0 annotations --> edge_label_array end end diff --git a/GraphRecipes/src/utils.jl b/GraphRecipes/src/utils.jl index 0200d14fe..e1286eda7 100644 --- a/GraphRecipes/src/utils.jl +++ b/GraphRecipes/src/utils.jl @@ -261,15 +261,8 @@ function process_edge_attribute(attr, source, destiny, weights) end return attr end -# Function from Plots/src/components.jl -"get an array of tuples of points on a circle with radius `r`" -function partialcircle(start_θ, end_θ, n = 20, r = 1) - return Tuple{Float64, Float64}[ - (r * cos(u), r * sin(u)) for u in range(start_θ, stop = end_θ, length = n) - ] -end -function partialcircle(start_θ, end_θ, circle_center::Array{T, 1}, n = 20, r = 1) where {T} +function PlotsBase.Shapes.partialcircle(start_θ, end_θ, circle_center::Array{T, 1}, n = 20, r = 1) where {T} return Tuple{Float64, Float64}[ (r * cos(u) + circle_center[1], r * sin(u) + circle_center[2]) for u in range(start_θ, stop = end_θ, length = n) diff --git a/GraphRecipes/test/functions.jl b/GraphRecipes/test/functions.jl index 88377a5cc..dab6dfc5f 100644 --- a/GraphRecipes/test/functions.jl +++ b/GraphRecipes/test/functions.jl @@ -1,3 +1,7 @@ +using AbstractTrees +using StableRNGs +using GraphRecipes.Graphs + function random_labelled_graph() n = 15 rng = StableRNG(1) diff --git a/GraphRecipes/test/parse_readme.jl b/GraphRecipes/test/parse_readme.jl index dbf30f9ab..8eaa22e2a 100644 --- a/GraphRecipes/test/parse_readme.jl +++ b/GraphRecipes/test/parse_readme.jl @@ -17,4 +17,4 @@ end # the installation instructions. readme_exprs = [Meta.parse("begin $(code_blocks[i]) end") for i in 2:length(code_blocks)] -julia_logo_pun() = eval(readme_exprs[1]) +readme_julia_logo_pun() = eval(readme_exprs[1]) diff --git a/GraphRecipes/test/runtests.jl b/GraphRecipes/test/runtests.jl index f5438478e..d9d052600 100644 --- a/GraphRecipes/test/runtests.jl +++ b/GraphRecipes/test/runtests.jl @@ -1,19 +1,17 @@ using VisualRegressionTests using AbstractTrees using LinearAlgebra -using GraphRecipes +using Logging using GraphRecipes using SparseArrays using ImageMagick using StableRNGs -using Logging using Graphs using Plots +using Plots.PlotsBase using Test using Gtk # for popup -const PlotsBase = Plots.PlotsBase - isci() = get(ENV, "CI", "false") == "true" itol(tol = nothing) = something(tol, isci() ? 1.0e-3 : 1.0e-5) @@ -22,45 +20,29 @@ include("parse_readme.jl") default(show = false, reuse = true) -@testset "functions" begin - rng = StableRNG(1) - for method in keys(GraphRecipes._graph_funcs) - method ≡ :spectral && continue # FIXME - dat = if (inp = GraphRecipes._graph_inputs[method]) ≡ :adjmat - [ - 0 1 1 - 1 0 1 - 1 1 0 - ] - elseif inp ≡ :sourcedestiny - Symmetric(sparse(rand(rng, 0:1, 8, 8))) - elseif inp ≡ :adjlist - dat = [ - 0 1 1 0 0 0 0 0 0 0 - 0 0 0 0 1 1 0 0 0 0 - 0 0 0 1 0 0 1 0 1 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 1 0 1 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - ] - else - @error "wrong input $inp" +cd(joinpath(@__DIR__, "..", "..", "assets", "GraphRecipes")) do + @testset "TestImages" begin + figure_files = readdir() + @testset "$figure_file" for figure_file in figure_files + figure = splitext(figure_file)[1] + if figure == "julia_type_tree" + if VERSION >= v"1.11" # julia 1.11 introduced Core.BFloat16 + @plottest julia_type_tree() "julia_type_tree.png" popup = !isci() tol = itol() + end + else + @plottest getproperty(@__MODULE__, Symbol(figure))() figure_file popup = !isci() tol = + itol() + end end - pl = graphplot(dat; method) - @test pl isa PlotsBase.Plot end end @testset "issues" begin @testset "143" begin g = SimpleGraph(7) + add_edge!(g, 2, 3) add_edge!(g, 3, 4) - @test g.ne == 2 al = GraphRecipes.get_adjacency_list(g) @test isempty(al[1]) @@ -96,22 +78,22 @@ end @testset "180" begin rng = StableRNG(1) mat = Symmetric(sparse(rand(rng, 0:1, 8, 8))) - graphplot(mat; method = :arcdiagram, rng) + graphplot(mat, method = :arcdiagram, rng = rng) end end @testset "utils.jl" begin rng = StableRNG(1) - @test GraphRecipes.directed_curve(0.0, 1.0, 0.0, 1.0; rng) == - GraphRecipes.directed_curve(0, 1, 0, 1; rng) + @test GraphRecipes.directed_curve(0.0, 1.0, 0.0, 1.0, rng = rng) == + GraphRecipes.directed_curve(0, 1, 0, 1, rng = rng) - @test GraphRecipes.isnothing(nothing) == PlotsBase.isnothing(nothing) - @test GraphRecipes.isnothing(missing) == PlotsBase.isnothing(missing) - @test GraphRecipes.isnothing(NaN) == PlotsBase.isnothing(NaN) - @test GraphRecipes.isnothing(0) == PlotsBase.isnothing(0) - @test GraphRecipes.isnothing(1) == PlotsBase.isnothing(1) - @test GraphRecipes.isnothing(0.0) == PlotsBase.isnothing(0.0) - @test GraphRecipes.isnothing(1.0) == PlotsBase.isnothing(1.0) + @test GraphRecipes.isnothing(nothing) == Plots.isnothing(nothing) + @test GraphRecipes.isnothing(missing) == Plots.isnothing(missing) + @test GraphRecipes.isnothing(NaN) == Plots.isnothing(NaN) + @test GraphRecipes.isnothing(0) == Plots.isnothing(0) + @test GraphRecipes.isnothing(1) == Plots.isnothing(1) + @test GraphRecipes.isnothing(0.0) == Plots.isnothing(0.0) + @test GraphRecipes.isnothing(1.0) == Plots.isnothing(1.0) for (s, e) in [(rand(rng), rand(rng)) for i in 1:100] @test GraphRecipes.partialcircle(s, e) == PlotsBase.partialcircle(s, e) @@ -144,12 +126,12 @@ end # checking that they don't error. Also, test all of the different aliases. @testset "Aliases" begin A = [1 0 1 0; 0 0 1 1; 1 1 1 1; 0 0 1 1] - graphplot(A; markercolor = :red, markershape = :rect, markersize = 0.5, rng) - graphplot(A; nodeweights = 1:4, rng) - graphplot(A; curvaturescalar = 0, rng) - graphplot(A; el = Dict((1, 2) => ""), elb = true, rng) - graphplot(A; ew = (s, d, w) -> 3, rng) - graphplot(A; ses = 0.5, rng) + graphplot(A, markercolor = :red, markershape = :rect, markersize = 0.5, rng = rng) + graphplot(A, nodeweights = 1:4, rng = rng) + graphplot(A, curvaturescalar = 0, rng = rng) + graphplot(A, el = Dict((1, 2) => ""), elb = true, rng = rng) + graphplot(A, ew = (s, d, w) -> 3, rng = rng) + graphplot(A, ses = 0.5, rng = rng) end end @@ -190,6 +172,6 @@ cd(joinpath(@__DIR__, "..", "..", "assets", "GraphRecipes")) do end @testset "README" begin - @plottest julia_logo_pun() "readme_julia_logo_pun.png" popup = !isci() tol = itol() + @plottest readme_julia_logo_pun() "readme_julia_logo_pun.png" popup = !isci() tol = itol() end end diff --git a/PlotsBase/Project.toml b/PlotsBase/Project.toml index 79744c514..7a747518a 100644 --- a/PlotsBase/Project.toml +++ b/PlotsBase/Project.toml @@ -101,8 +101,8 @@ Preferences = "1" Printf = "1" PythonPlot = "1" REPL = "1" +RecipesBase = "1.4.0" Random = "1" -RecipesBase = "1.3.1" RecipesPipeline = "1" Reexport = "1" Scratch = "1" diff --git a/PlotsBase/ext/GRExt.jl b/PlotsBase/ext/GRExt.jl index 75e117072..c84392c48 100644 --- a/PlotsBase/ext/GRExt.jl +++ b/PlotsBase/ext/GRExt.jl @@ -1,6 +1,6 @@ module GRExt -import PlotsBase: PlotsBase, PrecompileTools, RecipesPipeline, _cycle +import PlotsBase: PlotsBase, PrecompileTools, RecipesPipeline import NaNMath import GR @@ -331,11 +331,11 @@ function gr_getcolorind(c) return convert(Int, GR.inqcolorfromrgb(red(c), green(c), blue(c))) end -gr_set_linecolor(c) = GR.setlinecolorind(gr_getcolorind(_cycle(c, 1))) -gr_set_fillcolor(c) = GR.setfillcolorind(gr_getcolorind(_cycle(c, 1))) -gr_set_markercolor(c) = GR.setmarkercolorind(gr_getcolorind(_cycle(c, 1))) -gr_set_bordercolor(c) = GR.setbordercolorind(gr_getcolorind(_cycle(c, 1))) -gr_set_textcolor(c) = GR.settextcolorind(gr_getcolorind(_cycle(c, 1))) +gr_set_linecolor(c::Colorant) = GR.setlinecolorind(gr_getcolorind(c)) +gr_set_fillcolor(c::Colorant) = GR.setfillcolorind(gr_getcolorind(c)) +gr_set_markercolor(c::Colorant) = GR.setmarkercolorind(gr_getcolorind(c)) +gr_set_bordercolor(c::Colorant) = GR.setbordercolorind(gr_getcolorind(c)) +gr_set_textcolor(c::Colorant) = GR.settextcolorind(gr_getcolorind(c)) gr_set_transparency(α::Real) = GR.settransparency(clamp(α, 0, 1)) gr_set_transparency(::Nothing) = GR.settransparency(1) gr_set_transparency(c, α) = gr_set_transparency(α) @@ -518,7 +518,7 @@ function gr_polaraxes(rmin::Real, rmax::Real, sp::Subplot) # draw radial ticks yaxis[:showaxis] && for i in eachindex(rtick_values) r = (rtick_values[i] - rmin) / (rmax - rmin) - (r ≤ 1 && r ≥ 0) && gr_text(GR.wctondc(0.05, r)..., _cycle(rtick_labels, i)) + (r ≤ 1 && r ≥ 0) && gr_text(GR.wctondc(0.05, r)..., _getvalue(rtick_labels, i)) end GR.restorestate() return nothing @@ -1288,7 +1288,7 @@ function gr_add_legend(sp, leg, viewport_area) 1, min(max_markersize, mfac * msz), min(max_markersize, mfac * msw), - _cycle(msh, 1), + _getvalue(msh, 1), ) end @@ -2027,8 +2027,8 @@ function gr_draw_segments(series, x, y, z, fillrange, clims) if is2d && fillrange ≢ nothing (fc = get_fillcolor(series, clims, i)) |> gr_set_fillcolor gr_set_fillstyle(get_fillstyle(series, i)) - fx = _cycle(x, vcat(rng, reverse(rng))) - fy = vcat(_cycle(fr_from, rng), _cycle(fr_to, reverse(rng))) + fx = _getvalue(x, vcat(rng, reverse(rng))) + fy = vcat(_getvalue(fr_from, rng), _getvalue(RecipesBase.cycle(fr_to), reverse(rng))) gr_set_transparency(fc, get_fillalpha(series, i)) GR.fillarea(fx, fy) end @@ -2066,18 +2066,18 @@ function gr_draw_markers( rng = intersect(eachindex(IndexLinear(), x), segment.range) isempty(rng) && continue i = segment.attr_index - ms = get_thickness_scaling(series) * _cycle(msize, i) - msw = get_thickness_scaling(series) * _cycle(strokewidth, i) - shape = _cycle(shapes, i) + ms = get_thickness_scaling(series) * _getvalue(msize, i) + msw = get_thickness_scaling(series) * _getvalue(strokewidth, i) + shape = _getvalue(shapes, i) if !(shape isa Shape) shape = gr_get_markershape.(shape) end for j in rng gr_draw_marker( series, - _cycle(x, j), - _cycle(y, j), - _cycle(z, j), + _getvalue(x, j), + _getvalue(y, j), + _getvalue(z, j), clims, i, ms, @@ -2148,7 +2148,8 @@ function gr_draw_surface(series, x, y, z, clims) x, y, z = GR.gridit(x, y, z, nx, ny) end d_opt = get(e_kwargs, :display_option, GR.OPTION_COLORED_MESH) - if (!isnothing(fillalpha) && fillalpha < 1) || alpha(first(fillcolor)) < 1 + fc = fillcolor isa Colorant ? fillcolor : first(fillcolor) + if (!isnothing(fillalpha) && fillalpha < 1) || alpha(fc) < 1 gr_set_transparency(fillcolor, fillalpha) GR.surface(x, y, z, d_opt) else diff --git a/PlotsBase/ext/GastonExt.jl b/PlotsBase/ext/GastonExt.jl index fb136d8a2..4330ec8c3 100644 --- a/PlotsBase/ext/GastonExt.jl +++ b/PlotsBase/ext/GastonExt.jl @@ -327,9 +327,13 @@ function gaston_add_series(plt::Plot{GastonBackend}, series::Series) if !gsp.is3d && z ≡ nothing for (n, seg) in enumerate(series_segments(series, st; check = true)) i, rng = seg.attr_index, seg.range - fr = _cycle(series[:fillrange], 1:length(x[rng])) + fr = _getattr(series, :fillrange, 1:length(x[rng])) for sc in gaston_seriesconf!(sp, series, n == 1, i) - push!(gsp.plots, Gaston.Plot(x[rng], y[rng], fr, sc)) + if fr ≡ nothing + push!(gsp.plots, Gaston.Plot(x[rng], y[rng], sc)) + else + push!(gsp.plots, Gaston.Plot(x[rng], y[rng], fr, sc)) + end end end else @@ -774,8 +778,8 @@ gaston_lc_ls_lw(series::Series, clims, i::Int) = ( ) gaston_mk_ms_mc(series::Series, clims, i::Int) = ( - gaston_marker(_cycle(series[:markershape], i), get_markeralpha(series, i)), - 0.2_cycle(series[:markersize], i), + gaston_marker(_getattr(series, :markershape, i), get_markeralpha(series, i)), + 0.2 * _getattr(series, :markersize, i), gaston_color(get_markercolor(series, clims, i), get_markeralpha(series, i)), ) diff --git a/PlotsBase/ext/PGFPlotsXExt.jl b/PlotsBase/ext/PGFPlotsXExt.jl index 240f07557..463f8c9e4 100644 --- a/PlotsBase/ext/PGFPlotsXExt.jl +++ b/PlotsBase/ext/PGFPlotsXExt.jl @@ -551,7 +551,7 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o i, rng = segment.attr_index, segment.range segment_opt = pgfx_linestyle(opt, i) if opt[:markershape] ≢ :none - if (marker = _cycle(opt[:markershape], i)) isa Shape + if (marker = _getattr(opt, :markershape, i)) isa Shape scale_factor = 0.00125 msize = opt[:markersize] * scale_factor path = join( @@ -573,7 +573,7 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o # add fillrange if (sf = opt[:fillrange]) ≢ nothing && !isfilledcontour(series) if sf isa Number || sf isa AVec - pgfx_fillrange_series!(axis, series, series_func, i, _cycle(sf, rng), rng) + pgfx_fillrange_series!(axis, series, series_func, i, _getvalue(sf, rng), rng) elseif sf isa Tuple && series[:ribbon] ≢ nothing for sfi in sf pgfx_fillrange_series!( @@ -581,7 +581,7 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o series, series_func, i, - _cycle(sfi, rng), + _getvalue(sfi, rng), rng, ) end @@ -651,7 +651,7 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o end # for segments # get that last marker - if !isnothing(opt[:y]) && !any(isnan, opt[:y]) && opt[:markershape] isa AVec + if !isnothing(opt[:y]) && !any(isnan, opt[:y]) && (opt[:markershape] isa AVec || opt[:markershape] isa RecipesBase.CyclingAttribute) push!( axis, PGFPlotsX.PlotInc( # additional plot @@ -1174,7 +1174,7 @@ pgfx_should_add_to_legend(series::Series) = ) function pgfx_marker(plotattributes, i = 1) - shape = _cycle(plotattributes[:markershape], i) + shape = _getattr(plotattributes, :markershape, i) cstr = plot_color(get_markercolor(plotattributes, i), get_markeralpha(plotattributes, i)) cstr_stroke = plot_color( @@ -1184,9 +1184,10 @@ function pgfx_marker(plotattributes, i = 1) mark_size = pgfx_thickness_scaling(plotattributes) * 0.75 * - _cycle(plotattributes[:markersize], i) - mark_freq = if !any(isnan, plotattributes[:y]) && plotattributes[:markershape] isa AVec - length(plotattributes[:markershape]) + _getattr(plotattributes, :markersize, i) + ms = plotattributes[:markershape] + mark_freq = if !any(isnan, plotattributes[:y]) && (ms isa AVec || ms isa RecipesBase.CyclingAttribute) + length(ms) else 1 end @@ -1202,7 +1203,7 @@ function pgfx_marker(plotattributes, i = 1) "line width" => pgfx_thickness_scaling(plotattributes) * 0.75 * - _cycle(plotattributes[:markerstrokewidth], i), + _getattr(plotattributes, :markerstrokewidth, i), "rotate" => if shape ≡ :dtriangle 180 elseif shape ≡ :rtriangle @@ -1212,7 +1213,7 @@ function pgfx_marker(plotattributes, i = 1) else 0 end, - pgfx_get_linestyle(_cycle(plotattributes[:markerstrokestyle], i)) => + pgfx_get_linestyle(_getattr(plotattributes, :markerstrokestyle, i)) => nothing, ), ) @@ -1271,15 +1272,25 @@ end function pgfx_fillrange_attrs(fillrange, x, y) n = length(x) x_fill = [x; x[n:-1:1]; x[1]] - y_fill = [y; _cycle(fillrange, n:-1:1); y[1]] + fr_vals = if fillrange isa Number + fill(fillrange, n) + else + _getvalue(fillrange, n:-1:1) + end + y_fill = [y; fr_vals; y[1]] return PGFPlotsX.Coordinates(x_fill, y_fill) end function pgfx_fillrange_attrs(fillrange, x, y, z) n = length(x) x_fill = [x; x[n:-1:1]; x[1]] - y_fill = [y; y[n:-1:1]; x[1]] - z_fill = [z; _cycle(fillrange, n:-1:1); z[1]] + y_fill = [y; y[n:-1:1]; y[1]] + fr_vals = if fillrange isa Number + fill(fillrange, n) + else + _getvalue(fillrange, n:-1:1) + end + z_fill = [z; fr_vals; z[1]] return PGFPlotsX.Coordinates(x_fill, y_fill, z_fill) end diff --git a/PlotsBase/ext/PythonPlotExt.jl b/PlotsBase/ext/PythonPlotExt.jl index a4028c1f0..bb2d9022f 100644 --- a/PlotsBase/ext/PythonPlotExt.jl +++ b/PlotsBase/ext/PythonPlotExt.jl @@ -630,8 +630,8 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) ax.scatter( args...; zorder = zorder + 0.5, - marker = _py_marker(_cycle(series[:markershape], i)), - s = _py_thickness_scale(plt, _cycle(series[:markersize], i)) .^ 2, + marker = _py_marker(_getattr(series, :markershape, i)), + s = _py_thickness_scale(plt, _getattr(series, :markersize, i)) .^ 2, facecolors = _py_color( get_markercolor(series, i, cbar_scale), get_markeralpha(series, i), @@ -903,9 +903,9 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) f, dim1, dim2 = :fill_between, x[rng], y[rng] n = length(dim1) args = if typeof(fillrange) <: Union{Real, AVec} - dim1, _cycle(fillrange, rng), dim2 + dim1, _getvalue(fillrange, rng), dim2 elseif is_2tuple(fillrange) - dim1, _cycle(fillrange[1], rng), _cycle(fillrange[2], rng) + dim1, _getvalue(fillrange[1], rng), _getvalue(fillrange[2], rng) end la = get_linealpha(series, i) @@ -1596,7 +1596,7 @@ function _py_add_legend(plt::Plot, sp::Subplot, ax) solid_joinstyle = "miter", dash_capstyle = "butt", dash_joinstyle = "miter", - marker = _py_marker(_cycle(series[:markershape], 1)), + marker = _py_marker(_getattr(series, :markershape, 1)), markersize = _py_thickness_scale(plt, 0.8sp[:legend_font_pointsize]), markeredgecolor = _py_color( single_color(get_markerstrokecolor(series)), diff --git a/PlotsBase/src/Annotations.jl b/PlotsBase/src/Annotations.jl index 6dbf8ff0c..6db36b2ee 100644 --- a/PlotsBase/src/Annotations.jl +++ b/PlotsBase/src/Annotations.jl @@ -112,7 +112,7 @@ function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels) msize = Float64[] shapes = Vector{Shape}(undef, length(anns.strs)) for i in eachindex(anns.strs) - str = _cycle(anns.strs, i) + str = _getvalue(anns.strs, i) # get the width and height of the string (in mm) sw, sh = text_size(str, anns.font.pointsize) @@ -128,7 +128,7 @@ function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels) # and then re-scale a copy of baseshape to match the w/h ratio maxscale = max(xscale, yscale) push!(msize, maxscale) - baseshape = _cycle(anns.baseshape, i) + baseshape = _getvalue(anns.baseshape, i) shapes[i] = scale(baseshape, msw * xscale / maxscale, msh * yscale / maxscale, (0, 0)) end @@ -147,13 +147,13 @@ end function Base.iterate(ea::EachAnn, i = 1) (ea.anns ≡ nothing || isempty(ea.anns.strs) || i > length(ea.y)) && return - tmp = _cycle(ea.anns.strs, i) + tmp = _getvalue(ea.anns.strs, i) str, fnt = if isa(tmp, PlotText) tmp.str, tmp.font else tmp, ea.anns.font end - return (_cycle(ea.x, i), _cycle(ea.y, i), str, fnt), i + 1 + return (_getvalue(ea.x, i), _getvalue(ea.y, i), str, fnt), i + 1 end # ----------------------------------------------------------------------- @@ -213,7 +213,7 @@ _process_annotation_3d( function _process_annotation(sp::Subplot, ann, annotation_processor::Function) ann = makevec.(ann) - return [annotation_processor(sp, _cycle.(ann, i)...) for i in 1:maximum(length.(ann))] + return [annotation_processor(sp, _getvalue.(ann, i)...) for i in 1:maximum(length.(ann))] end # Expand arrays of coordinates, positions and labels into individual annotations diff --git a/PlotsBase/src/Commons/Commons.jl b/PlotsBase/src/Commons/Commons.jl index 66da3d435..272562353 100644 --- a/PlotsBase/src/Commons/Commons.jl +++ b/PlotsBase/src/Commons/Commons.jl @@ -13,7 +13,8 @@ export get_subplot, get_clims export fg_color, plot_color, single_color, alpha, isdark, color_or_nothing! export get_attr_symbol, - _cycle, + _getvalue, + _getattr, _as_gradient, makevec, maketuple, @@ -129,6 +130,35 @@ using ..RecipesBase: AbstractLayout include("layouts.jl") # --------------------------------------------------------------- +function _getvalue(val, args...) + return val +end +function _getvalue(val::RecipesBase.CyclingAttribute) + return val.value +end +function _getvalue(val::Union{AVec, PlotUtils.AbstractColorList, RecipesBase.CyclingAttribute}, i, args...) + return val[i] +end +function _getvalue(val::AMat, args...) + return val[args...] +end + +function _getattr(plotattr::Union{AKW, AbstractLayout}, key::Symbol, i = 1) + attr = plotattr[key] + return if attr isa PlotUtils.AbstractColorList + getindex(attr, mod1(i, length(attr))) + elseif attr isa RecipesBase.CyclingAttribute + # CyclingAttribute uses mod1 indexing internally + getindex(attr, i) + elseif attr isa AVec + getindex(attr, i) + elseif attr isa AMat + getindex(attr, :, i) + else + attr + end +end + wraptuple(x::Tuple) = x wraptuple(x) = (x,) @@ -209,17 +239,6 @@ let letter_keyword = Symbol(letter, keyword) end # ------------------------------------------------------------------------------------ -_cycle(v::AVec, idx::Int) = v[mod(idx, axes(v, 1))] -_cycle(v::AMat, idx::Int) = size(v, 1) == 1 ? v[end, mod(idx, axes(v, 2))] : v[:, mod(idx, axes(v, 2))] -_cycle(v, idx::Int) = v - -_cycle(v::AVec, indices::AVec{Int}) = map(i -> _cycle(v, i), indices) -_cycle(v::AMat, indices::AVec{Int}) = map(i -> _cycle(v, i), indices) -_cycle(v, indices::AVec{Int}) = fill(v, length(indices)) - -_cycle(cl::PlotUtils.AbstractColorList, idx::Int) = cl[mod1(idx, end)] -_cycle(cl::PlotUtils.AbstractColorList, idx::AVec{Int}) = cl[mod1.(idx, end)] - _as_gradient(grad) = grad _as_gradient(v::AbstractVector{<:Colorant}) = cgrad(v) _as_gradient(cp::ColorPalette) = cgrad(cp, categorical = true) @@ -279,11 +298,7 @@ end # helpers to figure out if there are NaN values in a list of array types anynan(i::Int, args::Tuple) = any( - a -> try - isnan(_cycle(a, i)) - catch MethodError - false - end, args + a -> isnan(_getvalue(a, i)), args ) anynan(args::Tuple) = i -> anynan(i, args) anynan(istart::Int, iend::Int, args::Tuple) = any(anynan(args), istart:iend) diff --git a/PlotsBase/src/Commons/aliases.jl b/PlotsBase/src/Commons/aliases.jl index d2328e888..201bf020d 100644 --- a/PlotsBase/src/Commons/aliases.jl +++ b/PlotsBase/src/Commons/aliases.jl @@ -1,5 +1,5 @@ autopick_ignore_none_auto(arr::AVec, idx::Integer) = - _cycle(setdiff(arr, [:none, :auto]), idx) + _getvalue(setdiff(arr, [:none, :auto]), idx) autopick_ignore_none_auto(notarr, idx::Integer) = notarr function aliases_and_autopick( diff --git a/PlotsBase/src/Commons/attrs.jl b/PlotsBase/src/Commons/attrs.jl index c7dae3b80..2d5f68719 100644 --- a/PlotsBase/src/Commons/attrs.jl +++ b/PlotsBase/src/Commons/attrs.jl @@ -1139,15 +1139,16 @@ has_black_border_for_default(st::Function) = has_black_border_for_default(st::Symbol) = like_histogram(st) || st in (:hexbin, :bar, :shape) -ensure_gradient!(plotattributes::AKW, csym::Symbol, asym::Symbol) = -if plotattributes[csym] isa ColorPalette - α = nothing - plotattributes[asym] isa AbstractVector || (α = plotattributes[asym]) - plotattributes[csym] = cgrad(plotattributes[csym], categorical = true, alpha = α) -elseif !(plotattributes[csym] isa ColorGradient) - plotattributes[csym] = - typeof(plotattributes[asym]) <: AbstractVector ? cgrad() : - cgrad(alpha = plotattributes[asym]) +function ensure_gradient!(plotattributes::AKW, csym::Symbol, asym::Symbol) + return if plotattributes[csym] isa ColorPalette + α = nothing + plotattributes[asym] isa AbstractVector || (α = plotattributes[asym]) + plotattributes[csym] = cgrad(alpha = α) + elseif !(plotattributes[csym] isa PlotUtils.AbstractColorList) + plotattributes[csym] = + typeof(plotattributes[asym]) <: AbstractVector ? cgrad() : + cgrad(alpha = plotattributes[asym]) + end end # get a good default linewidth... 0 for surface and heatmaps diff --git a/PlotsBase/src/DataSeries.jl b/PlotsBase/src/DataSeries.jl index 5e1cefc40..1c56e3a8b 100644 --- a/PlotsBase/src/DataSeries.jl +++ b/PlotsBase/src/DataSeries.jl @@ -25,11 +25,11 @@ import Base.show import ..PlotsBase import ..PlotUtils -using ..PlotsBase: DefaultsDict, RecipesPipeline, KW -using ..RecipesBase: @recipe +using ..PlotsBase: DefaultsDict, RecipesPipeline, AKW, KW +using ..RecipesBase: RecipesBase, @recipe, CyclingAttribute using ..Commons -mutable struct Series +mutable struct Series <: AKW plotattributes::DefaultsDict end @@ -41,11 +41,28 @@ end () end +Base.iterate(series::Series) = Base.iterate(series.plotattributes) +Base.iterate(series::Series, state) = Base.iterate(series.plotattributes, state) +Base.length(series::Series) = Base.length(series.plotattributes) Base.getindex(series::Series, k::Symbol) = series.plotattributes[k] Base.setindex!(series::Series, v, k::Symbol) = (series.plotattributes[k] = v) Base.get(series::Series, k::Symbol, v) = get(series.plotattributes, k, v) Base.push!(series::Series, args...) = extend_series!(series, args...) Base.append!(series::Series, args...) = extend_series!(series, args...) +function Commons._getattr(series::Series, key::Symbol, i::Union{Nothing, Number} = nothing) + attr = series[key] + ret = if attr isa PlotUtils.ColorGradient && i !== nothing + clims = get_clims(series) + get(attr, i, clims) + elseif (attr isa AVec || attr isa PlotUtils.ColorPalette) && i !== nothing + attr[i] + elseif attr isa AMat + getindex(attr, :, i) + else + attr + end + return ret +end should_add_to_legend(series::Series) = series.plotattributes[:primary] && @@ -130,17 +147,20 @@ for comp in (:line, :fill, :marker) i::Integer = 1, s::Symbol = :identity, ) - c = series[$Symbol($compcolor)] # series[:linecolor], series[:fillcolor], series[:markercolor] - z = series[$Symbol($comp_z)] # series[:line_z], series[:fill_z], series[:marker_z] + z = _getattr(series, $Symbol($comp_z), i) # series[:line_z], series[:fill_z], series[:marker_z] return if z ≡ nothing - isa(c, PlotUtils.ColorGradient) ? c : PlotUtils.plot_color(_cycle(c, i)) + c = _getattr(series, $Symbol($compcolor), i) # series[:linecolor], series[:fillcolor], series[:markercolor] + isa(c, PlotUtils.ColorGradient) ? c : PlotUtils.plot_color(c) else - grad = Commons.get_gradient(c) - if s ≡ :identity - get(grad, z[i], (cmin, cmax)) + grad = _getattr(series, $Symbol($compcolor)) # series[:linecolor], series[:fillcolor], series[:markercolor] + if !(grad isa PlotUtils.ColorGradient) + # z-coloring requires a gradient; if a plain color, just return it + PlotUtils.plot_color(grad) + elseif s ≡ :identity + get(grad, z, (cmin, cmax)) else base = _log_scale_bases[s] - get(grad, log(base, z[i]), (log(base, cmin), log(base, cmax))) + get(grad, log(base, z), (log(base, cmin), log(base, cmax))) end end end @@ -156,21 +176,20 @@ for comp in (:line, :fill, :marker) $get_compcolor(series, clims::NTuple{2, <:Number}, args...) = $get_compcolor(series, clims[1], clims[2], args...) - $get_compalpha(series, i::Integer = 1) = _cycle(series[$Symbol($compalpha)], i) + $get_compalpha(series, i::Integer = 1) = _getattr(series, $Symbol($compalpha), i) end end -get_linewidth(series, i::Integer = 1) = _cycle(series[:linewidth], i) -get_linestyle(series, i::Integer = 1) = _cycle(series[:linestyle], i) -get_fillstyle(series, i::Integer = 1) = _cycle(series[:fillstyle], i) +get_linewidth(series, i::Integer = 1) = _getattr(series, :linewidth, i) +get_linestyle(series, i::Integer = 1) = _getattr(series, :linestyle, i) +get_fillstyle(series, i::Integer = 1) = _getattr(series, :fillstyle, i) -get_markerstrokecolor(series, i::Integer = 1) = -let msc = series[:markerstrokecolor] - msc isa PlotUtils.ColorGradient ? msc : _cycle(msc, i) +function get_markerstrokecolor(series, i::Integer = 1) + return _getattr(series, :markerstrokecolor, i) end -get_markerstrokealpha(series, i::Integer = 1) = _cycle(series[:markerstrokealpha], i) -get_markerstrokewidth(series, i::Integer = 1) = _cycle(series[:markerstrokewidth], i) +get_markerstrokealpha(series, i::Integer = 1) = _getattr(series, :markerstrokealpha, i) +get_markerstrokewidth(series, i::Integer = 1) = _getattr(series, :markerstrokewidth, i) function get_colorgradient(series::Series) return if (st = series[:seriestype]) in (:surface, :heatmap) || isfilledcontour(series) @@ -242,9 +261,12 @@ end # we want to check if a series needs to be split into segments just because # of its attributes # check relevant attributes if they have multiple inputs +_is_segmenting_vector(v) = v isa AbstractVector && length(v) > 1 +_is_segmenting_vector(v::RecipesBase.CyclingAttribute) = length(v) > 1 + has_attribute_segments(series::Series) = any( - series[attr] isa AbstractVector && length(series[attr]) > 1 for + _is_segmenting_vector(series[attr]) for attr in PlotsBase.Commons._segmenting_vector_attributes ) || any( series[attr] isa AbstractArray for diff --git a/PlotsBase/src/Plots.jl b/PlotsBase/src/Plots.jl index 91144aaa0..6a18352be 100644 --- a/PlotsBase/src/Plots.jl +++ b/PlotsBase/src/Plots.jl @@ -73,8 +73,8 @@ struct InputWrapper{T} end protect(obj::T) where {T} = InputWrapper{T}(obj) Base.isempty(::InputWrapper) = false -Commons._cycle(wrapper::InputWrapper, ::Int) = wrapper.obj -Commons._cycle(wrapper::InputWrapper, ::AVec{Int}) = wrapper.obj +Commons._getvalue(wrapper::InputWrapper, ::Int) = wrapper.obj +Commons._getvalue(wrapper::InputWrapper, ::AVec{Int}) = wrapper.obj # ----------------------------------------------------------- diff --git a/PlotsBase/src/Subplots.jl b/PlotsBase/src/Subplots.jl index 8e275c3fe..386e54885 100644 --- a/PlotsBase/src/Subplots.jl +++ b/PlotsBase/src/Subplots.jl @@ -7,14 +7,15 @@ export Subplot, titlefont, get_series_color, needs_any_3d_axes -import PlotsBase -import ..RecipesPipeline: RecipesPipeline, DefaultsDict -import ..RecipesBase: AbstractLayout, AbstractBackend -import ..Commons: BoundingBox -import ..DataSeries: Series -import ..PlotUtils +using PlotsBase: PlotsBase +using ..RecipesPipeline: RecipesPipeline, DefaultsDict +using ..Commons: BoundingBox +using ..DataSeries: Series +using ..PlotUtils + +using ..RecipesBase: RecipesBase, AbstractLayout, AbstractBackend using ..Commons using ..Fonts using ..Ticks @@ -86,14 +87,20 @@ Commons.get_ticks(sp::Subplot, s::Symbol) = get_ticks(sp, sp[get_attr_symbol(s, # converts a symbol or string into a Colorant or ColorGradient # and assigns a color automatically -get_series_color(c, sp::Subplot, n::Int, seriestype) = - if c ≡ :auto - Commons.like_surface(seriestype) ? PlotsBase.cgrad() : _cycle(sp[:color_palette], n) -elseif isa(c, Int) - _cycle(sp[:color_palette], c) -else - c -end |> PlotsBase.plot_color +function get_series_color(c, sp::Subplot, n::Int, seriestype) + # Ensure valid index (n can be 0 for non-primary series before any primary series) + idx = max(1, n) + c = if c ≡ :auto + Commons.like_surface(seriestype) ? PlotsBase.cgrad() : _getattr(sp, :color_palette, idx) + elseif isa(c, Int) + _getattr(sp, :color_palette, c) + elseif c isa RecipesBase.CyclingAttribute + c[idx] + else + c + end + return PlotsBase.plot_color(c) +end get_series_color(c::AbstractArray, sp::Subplot, n::Int, seriestype) = map(x -> get_series_color(x, sp, n, seriestype), c) diff --git a/PlotsBase/src/examples.jl b/PlotsBase/src/examples.jl index aceca5875..f5d43cef5 100644 --- a/PlotsBase/src/examples.jl +++ b/PlotsBase/src/examples.jl @@ -235,6 +235,7 @@ const _examples = PlotExample[ to the plots. """, quote + # TODO: broken in plotly plot( PlotsBase.fakedata(100, 10), layout = 4, @@ -464,15 +465,14 @@ const _examples = PlotExample[ ), PlotExample( # 29 "Layouts, margins, label rotation, title location", - :(using PlotsBase.Commons), # for Measures, e.g. mm and px quote plot( rand(100, 6), layout = @layout([a b; c]), title = ["A" "B" "C"], titlelocation = :left, - left_margin = [20mm 0mm], - bottom_margin = 10px, + left_margin = [(20, :mm) (0, :mm)], + bottom_margin = (10, :px), xrotation = 60, ) end, @@ -542,7 +542,7 @@ const _examples = PlotExample[ p2 = plot(x, grid = (:y, :olivedrab, :dot, 1, 0.9), title = "Modified y grid") p3 = plot(deepcopy(p2), title = "Add x grid") xgrid!(p3, :on, :cadetblue, 2, :dashdot, 0.4) - plot(p1, p2, p3, layout = (1, 3), label = "", fillrange = 0, fillalpha = 0.3) + plot(p1, p2, p3, layout = (1, 3), label = "", fillrange = RecipesBase.cycle(0), fillalpha = 0.3) end, ), PlotExample( # 34 diff --git a/PlotsBase/src/pipeline.jl b/PlotsBase/src/pipeline.jl index 04aa75110..76e4a03fd 100644 --- a/PlotsBase/src/pipeline.jl +++ b/PlotsBase/src/pipeline.jl @@ -181,6 +181,8 @@ function RecipesPipeline.process_sliced_series_attributes!(::Plot, kw_list) # convert a ribbon into a fillrange if rib ≢ nothing make_fillrange_from_ribbon(kw) + # # unwrap CyclingAttribute after processing + rib isa RecipesBase.CyclingAttribute && (kw[:ribbon] = rib.value) # map fillrange if it's a Function elseif fr ≢ nothing && fr isa Function kw[:fillrange] = map(fr, kw[:x]) @@ -243,10 +245,9 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW}) for kw in kw_list # get the Subplot object to which the series belongs. sps = get(kw, :subplot, :auto) - sp = get_subplot( - plt, - _cycle(sps ≡ :auto ? plt.subplots : plt.subplots[sps], series_idx(kw_list, kw)), - ) + subplots = makevec(sps ≡ :auto ? plt.subplots : _getvalue(plt.subplots, sps)) + sidx = series_idx(kw_list, kw) + sp = getindex(RecipesBase.cycle(subplots), sidx) kw[:subplot] = sp # extract subplot/axis attributes from kw and add to sp_attr diff --git a/PlotsBase/src/plot.jl b/PlotsBase/src/plot.jl index 8b83b29b4..4a17083f7 100644 --- a/PlotsBase/src/plot.jl +++ b/PlotsBase/src/plot.jl @@ -59,9 +59,6 @@ function Base.show(io::IO, plt::Plot) return end -getplot(plt::Plot) = plt -getattr(plt::Plot, ::Int = 1) = plt.attr - # --------------------------------------------------------- """ diff --git a/PlotsBase/src/plotly.jl b/PlotsBase/src/plotly.jl index 6b4a232f3..f3a933136 100644 --- a/PlotsBase/src/plotly.jl +++ b/PlotsBase/src/plotly.jl @@ -896,7 +896,7 @@ function plotly_series(plt::Plot, series::Series) :symbol => get_plotly_marker(series[:markershape], string(series[:markershape])), # :opacity => series[:markeralpha], - :size => 2_cycle(series[:markersize], inds), + :size => 2 * _getattr(series, :markersize, inds), :color => rgba_string.( plot_color.(get_markercolor.(series, inds), get_markeralpha.(series, inds)), ), @@ -907,7 +907,7 @@ function plotly_series(plt::Plot, series::Series) get_markerstrokealpha.(series, inds), ), ), - :width => _cycle(series[:markerstrokewidth], inds), + :width => _getattr(series, :markerstrokewidth, inds), ), ) end @@ -976,7 +976,7 @@ function plotly_series_shapes(plt::Plot, series::Series, clims) end plotattributes_out[:showlegend] = k == 1 ? should_add_to_legend(series) : false plotly_polar!(plotattributes_out, series) - plotly_adjust_hover_label!(plotattributes_out, _cycle(series[:hover], i)) + plotly_adjust_hover_label!(plotattributes_out, _getattr(series, :hover, i)) plotattributes_outs[k] = merge(plotattributes_out, series[:extra_kwargs]) end if series[:fill_z] ≢ nothing @@ -1077,15 +1077,15 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z plotattributes_out[:marker] = KW( :symbol => get_plotly_marker( - _cycle(series[:markershape], i), - string(_cycle(series[:markershape], i)), + _getattr(series, :markershape, i), + string(_getattr(series, :markershape, i)), ), # :opacity => needs_scatter_fix ? [1, 0] : 1, - :size => 2_cycle(series[:markersize], i), + :size => 2 * _getattr(series, :markersize, i), :color => needs_scatter_fix ? [mcolor, mcolor_next] : mcolor, :line => KW( :color => needs_scatter_fix ? [lcolor, lcolor_next] : lcolor, - :width => _cycle(series[:markerstrokewidth], i), + :width => _getattr(series, :markerstrokewidth, i), ), ) end @@ -1111,7 +1111,7 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z end plotly_polar!(plotattributes_out, series) - plotly_adjust_hover_label!(plotattributes_out, _cycle(series[:hover], rng)) + plotly_adjust_hover_label!(plotattributes_out, _getattr(series, :hover, rng)) if hasfillrange # if hasfillrange is true, return two dictionaries (one for original diff --git a/PlotsBase/src/recipes.jl b/PlotsBase/src/recipes.jl index 094c9835c..30268ae0a 100644 --- a/PlotsBase/src/recipes.jl +++ b/PlotsBase/src/recipes.jl @@ -77,15 +77,15 @@ const POTENTIAL_VECTOR_ARGUMENTS = [ # sort vector arguments for arg in POTENTIAL_VECTOR_ARGUMENTS if typeof(plotattributes[arg]) <: AVec - plotattributes[arg] = _cycle(plotattributes[arg], indices) + plotattributes[arg] = _getattr(plotattributes, arg, indices) end end # a tuple as fillrange has to be handled differently if typeof(plotattributes[:fillrange]) <: Tuple lower, upper = plotattributes[:fillrange] - typeof(lower) <: AVec && (lower = _cycle(lower, indices)) - typeof(upper) <: AVec && (upper = _cycle(upper, indices)) + typeof(lower) <: AVec && (lower = _getvalue(lower, indices)) + typeof(upper) <: AVec && (upper = _getvalue(upper, indices)) plotattributes[:fillrange] = (lower, upper) end @@ -294,23 +294,25 @@ end # create vertical line segments from fill @recipe function f(::Type{Val{:sticks}}, x, y, z) # COV_EXCL_LINE n = length(x) - if (fr = plotattributes[:fillrange]) ≡ nothing - sp = plotattributes[:subplot] - fr = if sp[:yaxis][:scale] ≡ :identity - 0.0 - else - NaNMath.min(axis_limits(sp, :y)[1], ignorenan_minimum(y)) - end - end + newx, newy, newz = zeros(3n), zeros(3n), z ≢ nothing ? zeros(3n) : nothing for (i, (xi, yi, zi)) in enumerate(zip(x, y, z ≢ nothing ? z : 1:n)) + fri = _getattr(plotattributes, :fillrange, i) + if fri ≡ nothing + sp = plotattributes[:subplot] + fri = if sp[:yaxis][:scale] ≡ :identity + 0.0 + else + NaNMath.min(axis_limits(sp, :y)[1], ignorenan_minimum(y)) + end + end rng = (3i - 2):(3i) newx[rng] = [xi, xi, NaN] if z ≢ nothing newy[rng] = [yi, yi, NaN] - newz[rng] = [_cycle(fr, i), zi, NaN] + newz[rng] = [fri, zi, NaN] else - newy[rng] = [_cycle(fr, i), yi, NaN] + newy[rng] = [fri, yi, NaN] end end x := newx @@ -381,13 +383,13 @@ end for rng in DataSeries.iter_segments(args...) length(rng) < 2 && continue ts = range(0, stop = 1, length = npoints) - nanappend!(newx, map(t -> bezier_value(_cycle(x, rng), t), ts)) - nanappend!(newy, map(t -> bezier_value(_cycle(y, rng), t), ts)) + nanappend!(newx, map(t -> bezier_value(_getvalue(x, rng), t), ts)) + nanappend!(newy, map(t -> bezier_value(_getvalue(y, rng), t), ts)) if z ≢ nothing - nanappend!(newz, map(t -> bezier_value(_cycle(z, rng), t), ts)) + nanappend!(newz, map(t -> bezier_value(_getvalue(z, rng), t), ts)) end if fr ≢ nothing - nanappend!(newfr, map(t -> bezier_value(_cycle(fr, rng), t), ts)) + nanappend!(newfr, map(t -> bezier_value(_getvalue(fr, rng), t), ts)) end end @@ -442,7 +444,7 @@ end 1 end else - map(i -> 0.5_cycle(bw, i), eachindex(procx)) + map(i -> 0.5 * _getvalue(bw, i), eachindex(procx)) end # make fillto a vector... default fills to 0 @@ -466,10 +468,10 @@ end valid_i = isfinite.(procx) .& isfinite.(procy) for i in 1:ny valid_i[i] || continue - yi = procy[i] - center = procx[i] - hwi = _cycle(hw, i) - fi = _cycle(fillto, i) + yi = _getvalue(procy, i) + center = _getvalue(procx, i) + hwi = _getvalue(hw, i) + fi = _getvalue(fillto, i) push!(xseg, center - hwi, center - hwi, center + hwi, center + hwi, center - hwi) push!(yseg, yi, fi, fi, yi, yi) end @@ -680,24 +682,30 @@ end # create a secondary series for the markers if plotattributes[:markershape] ≢ :none + # create the primary path series first @series begin - seriestype := :scatter - x := _bin_centers(edge) - y := weights - fillrange := nothing - label := "" - primary := false + x := xpts + y := ypts + seriestype := :path + markershape := :none + xerror := :none + yerror := :none () end - markershape := :none - xerror := :none - yerror := :none + # then the non-primary scatter series for markers + seriestype := :scatter + x := _bin_centers(edge) + y := weights + fillrange := nothing + label := "" + primary := false + () + else + x := xpts + y := ypts + seriestype := :path + () end - - x := xpts - y := ypts - seriestype := :path - () end @deps stepbins path @@ -935,7 +943,7 @@ end θ_new = θ + 2π * y[i] / s coords = [(0.0, 0.0); partialcircle(θ, θ_new, 50)] @series begin - seriescolor := _cycle(colors, i) + seriescolor := _getvalue(colors, i) seriestype := :shape label --> string(x[i]) x := first.(coords) @@ -1127,10 +1135,10 @@ function error_coords(errorbar, errordata, otherdata...) od = map(odi -> Vector{float_extended_type(odi)}(undef, 0), otherdata) for (i, edi) in enumerate(errordata) for (j, odj) in enumerate(otherdata) - odi = _cycle(odj, i) + odi = _getvalue(odj, i) nanappend!(od[j], [odi, odi]) end - e1, e2 = error_tuple(_cycle(errorbar, i)) + e1, e2 = error_tuple(_getvalue(errorbar, i)) nanappend!(ed, [edi - e1, edi + e2]) end return (ed, od...) @@ -1215,11 +1223,11 @@ function quiver_using_arrows(plotattributes::AKW) is_3d && (z = zeros(0)) for i in 1:max(length(xorig), length(yorig), is_3d ? 0 : length(zorig)) # get the starting position - xi = _cycle(xorig, i) - yi = _cycle(yorig, i) - zi = is_3d ? _cycle(zorig, i) : 0 + xi = _getvalue(xorig, i) + yi = _getvalue(yorig, i) + zi = is_3d ? _getvalue(zorig, i) : 0 # get the velocity - vi = _cycle(velocity, i) + vi = _getvalue(velocity, i) if is_3d vx, vy, vz = if istuple(vi) vi[1], vi[2], vi[3] @@ -1264,12 +1272,12 @@ function quiver_using_hack(plotattributes::AKW) for i in 1:max(length(xorig), length(yorig)) # get the starting position - xi = _cycle(xorig, i) - yi = _cycle(yorig, i) + xi = _getvalue(xorig, i) + yi = _getvalue(yorig, i) p = P2((xi, yi)) # get the velocity - vi = _cycle(velocity, i) + vi = _getvalue(velocity, i) vx, vy = if istuple(vi) first(vi), last(vi) elseif isscalar(vi) diff --git a/PlotsBase/src/utils.jl b/PlotsBase/src/utils.jl index 32e0f93a2..e82f80f88 100644 --- a/PlotsBase/src/utils.jl +++ b/PlotsBase/src/utils.jl @@ -160,6 +160,8 @@ function slice_arg(v::AMat, idx::Int) end slice_arg(wrapper::InputWrapper, idx) = wrapper.obj slice_arg(v::NTuple{2, AMat}, idx::Int) = slice_arg(v[1], idx), slice_arg(v[2], idx) +slice_arg(v::RecipesBase.CyclingAttribute{<:AMat}, idx::Int) = RecipesBase.cycle(slice_arg(v.value, idx)) +slice_arg(v::RecipesBase.CyclingAttribute, idx) = v slice_arg(v, idx) = v """ @@ -191,6 +193,9 @@ function _slice_series_attrs!( commandIndex::Int, ) for k in keys(_series_defaults) + # skip ribbon matrix slicing - let make_fillrange_from_ribbon handle it + # (CyclingAttribute ribbons are still sliced via slice_arg dispatch) + k ≡ :ribbon && get(plotattributes, k, nothing) isa AMat && continue haskey(plotattributes, k) && slice_arg!(plotattributes, plotattributes, k, commandIndex, false) end @@ -319,7 +324,7 @@ end function make_fillrange_side(y::AVec, rib) frs = zeros(axes(y)) for (i, yi) in pairs(y) - frs[i] = yi + _cycle(rib, i) + frs[i] = yi + _getvalue(rib, i) end return frs end @@ -606,9 +611,6 @@ function with(f::Function, args...; scalefonts = nothing, kw...) for arg in args # change backend ? arg isa Symbol && if arg ∈ backends() - if (pkg = backend_package_name(arg)) ≢ nothing # :plotly - @eval Main import $pkg - end Base.invokelatest(backend, arg) end @@ -909,7 +911,7 @@ function _guess_best_legend_position(xl, yl, plt, weight = 100) for (i, lim) in enumerate(Iterators.product(xl, yl)) lim = lim ./ scale for ix in eachindex(x) - xi, yi = x[ix], _cycle(y, ix + yoffset) + xi, yi = x[ix], _getvalue(y, ix + yoffset) # ignore y points outside quadrant visible quadrant xi < xl[1] + quadrants[i][1][1] * (xl[2] - xl[1]) && continue xi > xl[1] + quadrants[i][1][2] * (xl[2] - xl[1]) && continue diff --git a/PlotsBase/test/test_layouts.jl b/PlotsBase/test/test_layouts.jl index 846da249d..8ce5de7dd 100644 --- a/PlotsBase/test/test_layouts.jl +++ b/PlotsBase/test/test_layouts.jl @@ -108,8 +108,6 @@ end io = devnull show(io, pl[1]) - @test PlotsBase.getplot(pl) == pl - @test PlotsBase.getattr(pl) == pl.attr @test PlotsBase.backend_object(pl) == pl.o @test occursin("Plot", string(pl)) print(io, pl) diff --git a/PlotsBase/test/test_misc.jl b/PlotsBase/test/test_misc.jl index e06c9bfea..8d8eb29ec 100644 --- a/PlotsBase/test/test_misc.jl +++ b/PlotsBase/test/test_misc.jl @@ -1,4 +1,8 @@ # miscellaneous tests (not fitting into other test files) +using PlotsBase +import GR +using PlotsBase: JSON, RecipesPipeline +using Test @testset "Infrastructure" begin @test_nowarn JSON.parse( @@ -149,21 +153,27 @@ end @test all(RecipesPipeline.get_axis_limits(p2, :x) .== x) end -@testset "Slicing" begin +@testset "Slicing & cycling" begin @test plot(1:5, fillrange = 0)[1][1][:fillrange] == 0 data4 = rand(4, 4) - mat = reshape(1:8, 2, 4) - sp = plot(data4, ribbon = (mat, mat))[1] + mat = 0.1 .* reshape(1:8, 2, 4) + # TODO: think about this case later + # sp = plot(data4, ribbon = (mat, mat))[1] for i in axes(data4, 1) - for attribute in (:fillrange, :ribbon) - nt = NamedTuple{tuple(attribute)} - get_attrs(pl) = pl[1][i][attribute] - @test plot(data4; nt(0)...) |> get_attrs == 0 - @test plot(data4; nt(Ref([1, 2]))...) |> get_attrs == [1.0, 2.0] - @test plot(data4; nt(Ref([1 2]))...) |> get_attrs == (iseven(i) ? 2 : 1) - @test plot(data4; nt(Ref(mat))...) |> get_attrs == [2(i - 1) + 1, 2i] - end - @test sp[i][:ribbon] == ([2(i - 1) + 1, 2i], [2(i - 1) + 1, 2i]) + @test plot(data4)[1][i][:seriescolor] == RGBA(palette(:default)[i]) + get_fillrange(pl) = pl[1][i][:fillrange] + @test plot(data4; fillrange = 0) |> get_fillrange == 0 + @test plot(data4; fillrange = [0.1, 0.2]) |> get_fillrange == [0.1, 0.2] + @test plot(data4; fillrange = [0.1 0.2]) |> get_fillrange == (iseven(i) ? 0.2 : 0.1) + @test plot(data4; fillrange = mat) |> get_fillrange == [0.1(2(i - 1) + 1), 0.2i] + get_ribbon(pl) = pl[1][i][:ribbon] + @test plot(data4; ribbon = 0) |> get_ribbon == 0 + @test_throws BoundsError plot(data4; ribbon = [0.1, 0.2]) + @test plot(data4; ribbon = RecipesBase.cycle([0.1, 0.2])) |> get_ribbon == [0.1, 0.2] + @test_throws BoundsError plot(data4; ribbon = [0.1 0.2]) + @test plot(data4; ribbon = RecipesBase.cycle([0.1 0.2])) |> get_ribbon == (iseven(i) ? 0.2 : 0.1) + @test plot(data4; ribbon = RecipesBase.cycle(mat)) |> get_ribbon == [0.1(2(i - 1) + 1), 0.2i] + # @test sp[i][:ribbon] == ([0.1(2(i - 1) + 1), 0.2i], [0.1(2(i - 1) + 1), 0.2i]) end end diff --git a/PlotsBase/test/test_pgfplotsx.jl b/PlotsBase/test/test_pgfplotsx.jl index 05f952df7..c71a9197d 100644 --- a/PlotsBase/test/test_pgfplotsx.jl +++ b/PlotsBase/test/test_pgfplotsx.jl @@ -1,4 +1,5 @@ using Test, PlotsBase, Unitful, LaTeXStrings +using RecipesBase: cycle const PGFPlotsX = Base.get_extension(PlotsBase, :PGFPlotsXExt).PGFPlotsX function create_plot(args...; kwargs...) @@ -347,9 +348,9 @@ with(:pgfplotsx) do @testset "Markers and Paths" begin pl = plot( 5 .- ones(9), - markershape = [:utriangle, :rect], + markershape = cycle([:utriangle, :rect]), markersize = 8, - color = [:red, :black], + color = cycle([:red, :black]), ) axis_contents = first(get_pgf_axes(pl)).contents plots = filter(x -> x isa PGFPlotsX.Plot, axis_contents) diff --git a/PlotsBase/test/test_utils.jl b/PlotsBase/test/test_utils.jl index 2366279c2..1bb066915 100644 --- a/PlotsBase/test/test_utils.jl +++ b/PlotsBase/test/test_utils.jl @@ -155,7 +155,7 @@ end @test segments([nan10; 1:5]) == [11:15] @test segments([1:5; nan10]) == [1:5] @test segments([nan10; 1:5; nan10; 1:5; nan10]) == [11:15, 26:30] - @test segments([NaN; 1], 1:10) == [2:2, 4:4, 6:6, 8:8, 10:10] + @test segments(RecipesBase.cycle([NaN; 1]), 1:10) == [2:2, 4:4, 6:6, 8:8, 10:10] @test segments([nan10; 1:15], [1:15; nan10]) == [11:15] end @@ -265,7 +265,7 @@ end # test cycling indexes x = 0.0:0.1:1 - y = [1, 2, 3] + y = RecipesBase.cycle([1, 2, 3]) pl = scatter(x, y) @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright diff --git a/RecipesBase/Project.toml b/RecipesBase/Project.toml index c3b0937e3..2a745a705 100644 --- a/RecipesBase/Project.toml +++ b/RecipesBase/Project.toml @@ -1,7 +1,7 @@ name = "RecipesBase" uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" author = ["Tom Breloff (@tbreloff)"] -version = "1.3.4" +version = "1.4.0" [deps] PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" diff --git a/RecipesBase/src/RecipesBase.jl b/RecipesBase/src/RecipesBase.jl index 08988c852..dc809c36c 100644 --- a/RecipesBase/src/RecipesBase.jl +++ b/RecipesBase/src/RecipesBase.jl @@ -10,7 +10,8 @@ export @recipe, RecipeData, AbstractBackend, AbstractPlot, - AbstractLayout + AbstractLayout, + cycle # Common abstract types for the Plots ecosystem abstract type AbstractBackend end @@ -52,6 +53,7 @@ apply_recipe(plotattributes::AbstractDict{Symbol, Any}) = () is_explicit(d::AbstractDict{Symbol, Any}, k) = haskey(d, k) function is_default end +include("api.jl") # -------------------------------------------------------------------------- # this holds the data and attributes of one series, and is returned from apply_recipe @@ -60,6 +62,48 @@ struct RecipeData args::Tuple end +struct CyclingAttribute{T} + value::T +end + +const CyclingContainerTypes = Union{AbstractArray, Tuple} + +function Base.getindex(c::CyclingAttribute, args...) + return c.value +end +function Base.getindex(c::CyclingAttribute{<:CyclingContainerTypes}, args...) + args = map(enumerate(args)) do (i, arg) + if arg isa Number + return mod1(arg, size(c.value, i)) + else + return arg + end + end + return Base.getindex(c.value, args...) +end + +function _cycling_index(v, i::Int) + n = length(v) + return firstindex(v) + mod(i - 1, n) +end + +Base.getindex(c::CyclingAttribute{<:CyclingContainerTypes}, i::Int) = Base.getindex(c.value, _cycling_index(c.value, i)) +Base.getindex(c::CyclingAttribute{<:CyclingContainerTypes}, i::StepRange) = map(i) do j + Base.getindex(c.value, _cycling_index(c.value, j)) +end +Base.getindex(c::CyclingAttribute, i::StepRange) = map(i) do j + Base.getindex(c.value, _cycling_index(c.value, j)) +end + +for op in (:+, :-, :/, :*) + @eval Base.$op(a::CyclingAttribute, b::CyclingAttribute) = CyclingAttribute($op(a.value, b.value)) +end +Base.:-(a::CyclingAttribute) = CyclingAttribute(-a.value) +Base.:(==)(a::CyclingAttribute, b::CyclingAttribute) = a.value == b.value +Base.firstindex(c::CyclingAttribute) = firstindex(c.value) +Base.lastindex(c::CyclingAttribute) = lastindex(c.value) +Base.length(c::CyclingAttribute) = length(c.value) +Base.size(c::CyclingAttribute, args...) = size(c.value, args...) # -------------------------------------------------------------------------- @inline to_symbol(s::Symbol) = s diff --git a/RecipesBase/src/api.jl b/RecipesBase/src/api.jl new file mode 100644 index 000000000..d88eec6ae --- /dev/null +++ b/RecipesBase/src/api.jl @@ -0,0 +1,6 @@ +""" + cycle(attr) + +Indicates that an attribute should be cycled. +""" +cycle(v) = CyclingAttribute(v) diff --git a/RecipesBase/test/runtests.jl b/RecipesBase/test/runtests.jl index 13f882d21..54bd5f913 100644 --- a/RecipesBase/test/runtests.jl +++ b/RecipesBase/test/runtests.jl @@ -7,6 +7,8 @@ using Test const KW = Dict{Symbol, Any} +include("test_api.jl") + RB.is_key_supported(k::Symbol) = true for t in map(i -> Symbol(:T, i), 1:5) diff --git a/RecipesBase/test/test_api.jl b/RecipesBase/test/test_api.jl new file mode 100644 index 000000000..931bdc18f --- /dev/null +++ b/RecipesBase/test/test_api.jl @@ -0,0 +1,30 @@ +using RecipesBase, Test + +@testset "Cycling attributes" begin + c1 = cycle([:red, :green]) + @test c1[1] == :red + @test c1[2] == :green + @test c1[3] == :red + @test c1[4] == :green + c2 = cycle([:red :green; :blue :yellow]) + @test c2[1] == :red + @test c2[2] == :blue + @test c2[3] == :green + @test c2[4] == :yellow + @test c2[5] == :red + @test c2[6] == :blue + @test c2[7] == :green + @test c2[8] == :yellow + @test c2[1, :] == [:red, :green] + @test c2[:, 1] == [:red, :blue] + @test c2[3, :] == [:red, :green] + @test c2[:, 3] == [:red, :blue] + c3 = cycle(:blue) + @test c3[1] == :blue + @test c3[2] == :blue + c4 = cycle((1, 2)) + @test c4[1] == 1 + @test c4[2] == 2 + @test c4[3] == 1 + @test c4[4] == 2 +end diff --git a/RecipesPipeline/Project.toml b/RecipesPipeline/Project.toml index 4a28c04eb..27c5f77ce 100644 --- a/RecipesPipeline/Project.toml +++ b/RecipesPipeline/Project.toml @@ -16,6 +16,6 @@ RecipesBase = {path = "../RecipesBase"} [compat] NaNMath = "1" PlotUtils = "1" -RecipesBase = "1.3.1" +RecipesBase = "1.4.0" PrecompileTools = "1" julia = "1.10" diff --git a/RecipesPipeline/src/series.jl b/RecipesPipeline/src/series.jl index 6cad52ffc..11c898c3b 100644 --- a/RecipesPipeline/src/series.jl +++ b/RecipesPipeline/src/series.jl @@ -32,6 +32,7 @@ _prepare_series_data(s::Surface{<:AMat{<:MaybeNumber}}) = _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) +_prepare_series_data(c::RecipesBase.CyclingAttribute) = c # default: assume x represents a single series _series_data_vector(x, plotattributes) = [_prepare_series_data(x)] diff --git a/StatsPlots/ext/InteractExt.jl b/StatsPlots/ext/InteractExt.jl index 55fc708a7..51899e0cc 100644 --- a/StatsPlots/ext/InteractExt.jl +++ b/StatsPlots/ext/InteractExt.jl @@ -78,7 +78,7 @@ function StatsPlots.dataviewer(t; throttle = 0.1, nbins = 30, nbins_range = 1:10 # grouping kwarg has_by = by_toggle[] && !isempty(by[]) - by_tup = Tuple(getindex(&dict, b) for b in by[]) + by_tup = Tuple(PlotsBase.Commons._getvalue(&dict, b) for b in by[]) has_by && (kwargs[:group] = NamedTuple{Tuple(by[])}(by_tup)) # label kwarg diff --git a/StatsPlots/src/StatsPlots.jl b/StatsPlots/src/StatsPlots.jl index 36300e2a0..e5a5f2a23 100644 --- a/StatsPlots/src/StatsPlots.jl +++ b/StatsPlots/src/StatsPlots.jl @@ -4,7 +4,7 @@ using Reexport import RecipesBase: recipetype using RecipesPipeline @reexport using PlotsBase -import PlotsBase.Commons: _cycle, mm +import PlotsBase.Commons: mm using LinearAlgebra: eigen, diagm using Distributions diff --git a/StatsPlots/src/bar.jl b/StatsPlots/src/bar.jl index 8d6b9f5fe..6d68cae76 100644 --- a/StatsPlots/src/bar.jl +++ b/StatsPlots/src/bar.jl @@ -38,7 +38,7 @@ grouped_xy(y::AbstractArray) = 1:size(y, 1), y bar_width := bws * clamp(1 - spacing, 0, 1) xmat = zeros(nr, nc) for r in 1:nr - bw = _cycle(bws, r) + bw = PlotsBase.Commons._getvalue(bws, r) farleft = xnums[r] - 0.5 * (bw * nc) for c in 1:nc xmat[r, c] = farleft + 0.5bw + (c - 1) * bw @@ -59,9 +59,13 @@ grouped_xy(y::AbstractArray) = 1:size(y, 1), y # compute fillrange y, fr = isstack ? groupedbar_fillrange(y) : - (y, get(plotattributes, :fillrange, [fill_bottom])) + (y, get(plotattributes, :fillrange, fill_bottom)) if isylog - replace!(fr, 0 => fill_bottom) + if fr isa AbstractArray + replace!(fr, 0 => fill_bottom) + elseif fr == 0 + fr = fill_bottom + end end fillrange := fr diff --git a/StatsPlots/src/boxplot.jl b/StatsPlots/src/boxplot.jl index e59225d5b..8f48ba5fc 100644 --- a/StatsPlots/src/boxplot.jl +++ b/StatsPlots/src/boxplot.jl @@ -20,7 +20,7 @@ notch_width(q2, q4, N) = 1.58 * (q4 - q2) / sqrt(N) x = if step(x) == first(x) == 1 plotattributes[:series_plotindex] else - [getindex(x, plotattributes[:series_plotindex])] + [PlotsBase.Commons._getvalue(x, plotattributes[:series_plotindex])] end end xsegs, ysegs = PlotsBase.Segments(), PlotsBase.Segments() @@ -34,7 +34,7 @@ notch_width(q2, q4, N) = 1.58 * (q4 - q2) / sqrt(N) ww = whisker_width ≡ :match ? bw : whisker_width ≡ :half ? bw / 2 : whisker_width for (i, glabel) in enumerate(sort(glabels; by = sort_labels_by)) # filter y - values = y[filter(i -> _cycle(x, i) == glabel, 1:length(y))] + values = y[filter(i -> PlotsBase.Commons._getvalue(x, i) == glabel, 1:length(y))] # compute quantiles q1, q2, q3, q4, q5 = quantile(values, range(0, stop = 1, length = 5)) @@ -50,8 +50,8 @@ notch_width(q2, q4, N) = 1.58 * (q4 - q2) / sqrt(N) # make the shape center = PlotsBase.discrete_value!(plotattributes, :x, glabel)[1] + xshift - hw = 0.5_cycle(bw, i) # Box width - HW = 0.5_cycle(ww, i) # Whisker width + hw = 0.5 * PlotsBase.Commons._getvalue(bw, i) # Box width + HW = 0.5 * PlotsBase.Commons._getvalue(ww, i) # Whisker width l, m, r = center - hw, center, center + hw lw, rw = center - HW, center + HW @@ -246,7 +246,7 @@ recipetype(::Val{:groupedboxplot}, args...) = GroupedBoxplot(args) bar_width := bws * clamp(1 - spacing, 0, 1) for i in 1:n groupinds = idxs[i] - Δx = _cycle(bws, i) * (i - (n + 1) / 2) + Δx = PlotsBase.Commons._getvalue(bws, i) * (i - (n + 1) / 2) x[groupinds] .+= Δx end end diff --git a/StatsPlots/src/cornerplot.jl b/StatsPlots/src/cornerplot.jl index 03824bbf4..7a9827deb 100644 --- a/StatsPlots/src/cornerplot.jl +++ b/StatsPlots/src/cornerplot.jl @@ -101,11 +101,11 @@ recipetype(::Val{:cornerplot}, args...) = CornerPlot(args) ticks := :auto if i == N xformatter := :auto - xguide := _cycle(labs, j) + xguide := PlotsBase.Commons._getvalue(labs, j) end if j == 1 yformatter := :auto - yguide := _cycle(labs, i) + yguide := PlotsBase.Commons._getvalue(labs, i) end seriestype := :scatter subplot := indices[i + 1 - k, j] diff --git a/StatsPlots/src/corrplot.jl b/StatsPlots/src/corrplot.jl index 8f1fc72cf..f411f76d9 100644 --- a/StatsPlots/src/corrplot.jl +++ b/StatsPlots/src/corrplot.jl @@ -32,11 +32,11 @@ Meant to be overloaded by other types! to_corrplot_matrix(x) = x function update_ticks_guides(d::KW, labs, i, j, n) - # d[:title] = (i==1 ? _cycle(labs,j) : "") + # d[:title] = (i==1 ? PlotsBase.Commons._getvalue(labs,j) : "") # d[:xticks] = (i==n) - d[:xguide] = (i == n ? _cycle(labs, j) : "") + d[:xguide] = (i == n ? PlotsBase.Commons._getvalue(labs, j) : "") # d[:yticks] = (j==1) - return d[:yguide] = (j == 1 ? _cycle(labs, i) : "") + return d[:yguide] = (j == 1 ? PlotsBase.Commons._getvalue(labs, i) : "") end @recipe function f(cp::CorrPlot) diff --git a/StatsPlots/src/dotplot.jl b/StatsPlots/src/dotplot.jl index a489f7039..c1d5eaf7d 100644 --- a/StatsPlots/src/dotplot.jl +++ b/StatsPlots/src/dotplot.jl @@ -7,7 +7,7 @@ if step(x) == first(x) == 1 x = plotattributes[:series_plotindex] else - x = [getindex(x, plotattributes[:series_plotindex])] + x = [PlotsBase.Commons._getvalue(x, plotattributes[:series_plotindex])] end end @@ -23,10 +23,10 @@ for (i, grouplabel) in enumerate(grouplabels) # filter y - groupy = y[filter(i -> _cycle(x, i) == grouplabel, 1:length(y))] + groupy = y[filter(i -> PlotsBase.Commons._getvalue(x, i) == grouplabel, 1:length(y))] center = PlotsBase.discrete_value!(plotattributes, :x, grouplabel)[1] - halfwidth = 0.5_cycle(barwidth, i) + halfwidth = 0.5PlotsBase.Commons._getvalue(barwidth, i) offsets = getoffsets(halfwidth, groupy) @@ -103,7 +103,7 @@ recipetype(::Val{:groupeddotplot}, args...) = GroupedDotplot(args) bar_width := bws * clamp(1 - spacing, 0, 1) for i in 1:n groupinds = idxs[i] - Δx = _cycle(bws, i) * (i - (n + 1) / 2) + Δx = PlotsBase.Commons._getvalue(bws, i) * (i - (n + 1) / 2) x[groupinds] .+= Δx end end diff --git a/StatsPlots/src/violin.jl b/StatsPlots/src/violin.jl index b8319dc3a..e2202863b 100644 --- a/StatsPlots/src/violin.jl +++ b/StatsPlots/src/violin.jl @@ -42,7 +42,7 @@ get_quantiles(n::Int) = range(0, 1, length = n + 2)[2:(end - 1)] x = if step(x) == first(x) == 1 plotattributes[:series_plotindex] else - [getindex(x, plotattributes[:series_plotindex])] + [PlotsBase.Commons._getvalue(x, plotattributes[:series_plotindex])] end end xsegs, ysegs = PlotsBase.Segments(), PlotsBase.Segments() @@ -53,7 +53,7 @@ get_quantiles(n::Int) = range(0, 1, length = n + 2)[2:(end - 1)] bw == nothing && (bw = 0.8) msc = plotattributes[:markerstrokecolor] for (i, glabel) in enumerate(glabels) - fy = y[filter(i -> _cycle(x, i) == glabel, 1:length(y))] + fy = y[filter(i -> PlotsBase.Commons._getvalue(x, i) == glabel, 1:length(y))] widths, centers = violin_coords( fy, trim = trim, @@ -63,7 +63,7 @@ get_quantiles(n::Int) = range(0, 1, length = n + 2)[2:(end - 1)] isempty(widths) && continue # normalize - hw = 0.5_cycle(bw, i) + hw = 0.5PlotsBase.Commons._getvalue(bw, i) widths = hw * widths / PlotsBase.ignorenan_maximum(widths) # make the violin @@ -202,7 +202,7 @@ recipetype(::Val{:groupedviolin}, args...) = GroupedViolin(args) bar_width := bws * clamp(1 - spacing, 0, 1) for i in 1:n groupinds = idxs[i] - Δx = _cycle(bws, i) * (i - (n + 1) / 2) + Δx = PlotsBase.Commons._getvalue(bws, i) * (i - (n + 1) / 2) x[groupinds] .+= Δx end end diff --git a/assets/PlotsBase/gr/0.1.0/ref022.png b/assets/PlotsBase/gr/0.1.0/ref022.png index c62ae1c9e..40582f232 100644 Binary files a/assets/PlotsBase/gr/0.1.0/ref022.png and b/assets/PlotsBase/gr/0.1.0/ref022.png differ