diff --git a/src/args.jl b/src/args.jl index 2452dfdf..12e75cdd 100644 --- a/src/args.jl +++ b/src/args.jl @@ -54,7 +54,7 @@ const _3dTypes = [ :path3d, :scatter3d, :surface, :wireframe, :contour3d, :volume, :mesh3d ] const _allTypes = vcat([ - :none, :line, :path, :steppre, :steppost, :sticks, :scatter, + :none, :line, :path, :steppre, :stepmid, :steppost, :sticks, :scatter, :heatmap, :hexbin, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :histogram3d, :density, :bar, :hline, :vline, @@ -101,7 +101,7 @@ const _typeAliases = Dict{Symbol,Symbol}( add_non_underscore_aliases!(_typeAliases) const _histogram_like = [:histogram, :barhist, :barbins] -const _line_like = [:line, :path, :steppre, :steppost] +const _line_like = [:line, :path, :steppre, :stepmid, :steppost] const _surface_like = [:contour, :contourf, :contour3d, :heatmap, :surface, :wireframe, :image] like_histogram(seriestype::Symbol) = seriestype in _histogram_like diff --git a/src/axes.jl b/src/axes.jl index 7173999e..e2f031ad 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -441,7 +441,7 @@ function widen(lmin, lmax, scale = :identity) end # figure out if widening is a good idea. -const _widen_seriestypes = (:line, :path, :steppre, :steppost, :sticks, :scatter, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :bar, :shape, :path3d, :scatter3d) +const _widen_seriestypes = (:line, :path, :steppre, :stepmid, :steppost, :sticks, :scatter, :barbins, :barhist, :histogram, :scatterbins, :scatterhist, :stepbins, :stephist, :bins2d, :histogram2d, :bar, :shape, :path3d, :scatter3d) function default_should_widen(axis::Axis) should_widen = false diff --git a/src/backends.jl b/src/backends.jl index c6c54f22..85ebfe98 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -555,6 +555,7 @@ const _pyplot_attr = merge_with_base_supported([ const _pyplot_seriestype = [ :path, :steppre, + :stepmid, :steppost, :shape, :straightline, @@ -636,6 +637,7 @@ const _hdf5_attr = merge_with_base_supported([ const _hdf5_seriestype = [ :path, :steppre, + :stepmid, :steppost, :shape, :straightline, @@ -707,7 +709,7 @@ const _inspectdr_attr = merge_with_base_supported([ ]) const _inspectdr_style = [:auto, :solid, :dash, :dot, :dashdot] const _inspectdr_seriestype = [ - :path, :scatter, :shape, :straightline, #, :steppre, :steppost + :path, :scatter, :shape, :straightline, #, :steppre, :stepmid, :steppost ] #see: _allMarkers, _shape_keys const _inspectdr_marker = Symbol[ diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 5e1425c7..c5dd04b3 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -571,6 +571,26 @@ end gr_view_xcenter(viewport_plotarea) = 0.5 * (viewport_plotarea[1] + viewport_plotarea[2]) gr_view_ycenter(viewport_plotarea) = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4]) +gr_view_xposition(viewport_plotarea, position) = viewport_plotarea[1] + position * (viewport_plotarea[2] - viewport_plotarea[1]) +gr_view_yposition(viewport_plotarea, position) = viewport_plotarea[3] + position * (viewport_plotarea[4] - viewport_plotarea[3]) + +function position(symb) + if symb == :top || symb == :right + return 0.95 + elseif symb == :left || symb == :bottom + return 0.05 + end + return 0.5 +end + +function alignment(symb) + if symb == :top || symb == :right + return GR.TEXT_HALIGN_RIGHT + elseif symb == :left || symb == :bottom + return GR.TEXT_HALIGN_LEFT + end + return GR.TEXT_HALIGN_CENTER +end # -------------------------------------------------------------------------------------- @@ -1492,21 +1512,29 @@ function gr_label_axis(sp, letter, viewport_plotarea) if letter === :y w = 0.03 + gr_axis_width(sp, axis) GR.setcharup(-1, 0) + # + yposition = gr_view_yposition(viewport_plotarea, position(axis[:guidefontvalign])) + yalign = alignment(axis[:guidefontvalign]) + # if guide_position == :right || (guide_position == :auto && axis[:mirror]) - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) - gr_text(viewport_plotarea[2] + w, gr_view_ycenter(viewport_plotarea), axis[:guide]) + GR.settextalign(yalign, GR.TEXT_VALIGN_BOTTOM) + gr_text(viewport_plotarea[2] + w, yposition, axis[:guide]) else - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) - gr_text(viewport_plotarea[1] - w, gr_view_ycenter(viewport_plotarea), axis[:guide]) + GR.settextalign(yalign, GR.TEXT_VALIGN_TOP) + gr_text(viewport_plotarea[1] - w, yposition, axis[:guide]) end else h = 0.015 + gr_axis_height(sp, axis) + # + xposition = gr_view_xposition(viewport_plotarea, position(axis[:guidefonthalign])) + xalign = alignment(axis[:guidefonthalign]) + # if guide_position == :top || (guide_position == :auto && axis[:mirror]) - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) - gr_text(gr_view_xcenter(viewport_plotarea), viewport_plotarea[4] + h, axis[:guide]) + GR.settextalign(xalign, GR.TEXT_VALIGN_TOP) + gr_text(xposition, viewport_plotarea[4] + h, axis[:guide]) else - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) - gr_text(gr_view_xcenter(viewport_plotarea), viewport_plotarea[3] - h, axis[:guide]) + GR.settextalign(xalign, GR.TEXT_VALIGN_BOTTOM) + gr_text(xposition, viewport_plotarea[3] - h, axis[:guide]) end end GR.restorestate() diff --git a/src/backends/inspectdr.jl b/src/backends/inspectdr.jl index 005d3da2..09f576b9 100644 --- a/src/backends/inspectdr.jl +++ b/src/backends/inspectdr.jl @@ -17,9 +17,6 @@ Add in functionality to Plots.jl: is_marker_supported(::InspectDRBackend, shape::Shape) = true -_inspectdr_to_pixels(bb::BoundingBox) = - InspectDR.BoundingBox(to_pixels(left(bb)), to_pixels(right(bb)), to_pixels(top(bb)), to_pixels(bottom(bb))) - #Do we avoid Map to avoid possible pre-comile issues? function _inspectdr_mapglyph(s::Symbol) s == :rect && return :square @@ -290,7 +287,7 @@ For st in :shape: color = linecolor, fillcolor = fillcolor ) end - elseif st in (:path, :scatter, :straightline) #, :steppre, :steppost) + elseif st in (:path, :scatter, :straightline) #, :steppre, :stepmid, :steppost) #NOTE: In Plots.jl, :scatter plots have 0-linewidths (I think). linewidth = series[:linewidth] #More efficient & allows some support for markerstrokewidth: @@ -476,11 +473,16 @@ end function _update_plot_object(plt::Plot{InspectDRBackend}) mplot = _inspectdr_getmplot(plt.o) if nothing == mplot; return; end + mplot.bblist = InspectDR.BoundingBox[] for (i, sp) in enumerate(plt.subplots) - graphbb = _inspectdr_to_pixels(plotarea(sp)) - plot = mplot.subplots[i] - plot.plotbb = InspectDR.plotbounds(plot.layout.values, graphbb) + figw, figh = sp.plt[:size] + pcts = bbox_to_pcts(sp.bbox, figw*px, figh*px) + _left, _bottom, _width, _height = pcts + ymax = 1.0-_bottom + ymin = ymax - _height + bb = InspectDR.BoundingBox(_left, _left+_width, ymin, ymax) + push!(mplot.bblist, bb) end gplot = _inspectdr_getgui(plt.o) diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index a9aff316..9ff603da 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -789,6 +789,8 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z :width => get_linewidth(series, i), :shape => if st == :steppre "vh" + elseif st == :stepmid + "hvh" elseif st == :steppost "hv" else diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index aca6bb64..cb7c4e59 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -152,12 +152,14 @@ end function py_stepstyle(seriestype::Symbol) seriestype == :steppost && return "steps-post" + seriestype == :stepmid && return "steps-mid" seriestype == :steppre && return "steps-pre" return "default" end function py_fillstepstyle(seriestype::Symbol) seriestype == :steppost && return "post" + seriestype == :stepmid && return "mid" seriestype == :steppre && return "pre" return nothing end @@ -206,13 +208,13 @@ function fix_xy_lengths!(plt::Plot{PyPlotBackend}, series::Series) end function py_linecolormap(series::Series) - py_colormap(cgrad(get_linecolor(series), alpha=get_linealpha(series))) + py_colormap(cgrad(series[:linecolor], alpha=get_linealpha(series))) end function py_markercolormap(series::Series) - py_colormap(cgrad(get_markercolor(series), alpha=get_markeralpha(series))) + py_colormap(cgrad(series[:markercolor], alpha=get_markeralpha(series))) end function py_fillcolormap(series::Series) - py_colormap(cgrad(get_fillcolor(series), alpha=get_fillalpha(series))) + py_colormap(cgrad(series[:fillcolor], alpha=get_fillalpha(series))) end # --------------------------------------------------------------------------- @@ -408,7 +410,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) # for each plotting command, optionally build and add a series handle to the list # line plot - if st in (:path, :path3d, :steppre, :steppost, :straightline) + if st in (:path, :path3d, :steppre, :stepmid, :steppost, :straightline) if maximum(series[:linewidth]) > 0 # TODO: check LineCollection alternative for speed # if length(segments) > 1 && (any(typeof(series[attr]) <: AbstractVector for attr in (:fillcolor, :fillalpha)) || series[:fill_z] !== nothing) && !(typeof(series[:linestyle]) <: AbstractVector) @@ -485,7 +487,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) # add markers? if series[:markershape] != :none && st in ( - :path, :scatter, :path3d, :scatter3d, :steppre, :steppost, :bar + :path, :scatter, :path3d, :scatter3d, :steppre, :stepmid, :steppost, :bar ) for segment in series_segments(series, :scatter) i, rng = segment.attr_index, segment.range @@ -1363,7 +1365,7 @@ function py_add_legend(plt::Plot, sp::Subplot, ax) linestyle = py_linestyle(series[:seriestype], get_linestyle(series)), capstyle = "butt" ) - elseif series[:seriestype] in (:path, :straightline, :scatter, :steppre, :steppost) + elseif series[:seriestype] in (:path, :straightline, :scatter, :steppre, :stepmid, :steppost) hasline = get_linewidth(series) > 0 PyPlot.plt."Line2D"((0, 1),(0,0), color = py_color(single_color(get_linecolor(series, clims)), get_linealpha(series)), diff --git a/src/examples.jl b/src/examples.jl index cfc8e96f..c834be44 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -1119,6 +1119,25 @@ const _examples = PlotExample[ quiver(x,y,z, quiver=(u,v,w)) end] ), + PlotExample( # 53 + "Step Types", + "A comparison of the various step-like `seriestype`s", + [ + :( + begin + x = 1:5 + y = [1, 2, 3, 2, 1] + default(shape=:circle) + plot( + plot(x, y, markershape=:circle, seriestype=:steppre, label="steppre"), + plot(x, y, markershape=:circle, seriestype=:stepmid, label="stepmid"), + plot(x, y, markershape=:circle, seriestype=:steppost, label="steppost"), + layout=(3,1) + ) + end + ), + ], + ), ] # Some constants for PlotDocs and PlotReferenceImages diff --git a/src/recipes.jl b/src/recipes.jl index ed6f2331..d0fbf136 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -181,33 +181,37 @@ end # --------------------------------------------------------------------------- # steps -make_steps(x, st) = x -function make_steps(x::AbstractArray, st) +make_steps(x, st, even) = x +function make_steps(x::AbstractArray, st, even) n = length(x) n == 0 && return zeros(0) - newx = zeros(2n - 1) - for i = 1:n + newx = zeros(2n - (even ? 0 : 1)) + newx[1] = x[1] + for i = 2:n idx = 2i - 1 - newx[idx] = x[i] - if i > 1 + if st == :mid + newx[idx] = newx[idx-1] = (x[i] + x[i-1]) / 2 + else + newx[idx] = x[i] newx[idx - 1] = x[st == :pre ? i : i - 1] end end + even && (newx[end] = x[end]) return newx end -make_steps(t::Tuple, st) = Tuple(make_steps(ti, st) for ti in t) +make_steps(t::Tuple, st, even) = Tuple(make_steps(ti, st, even) for ti in t) @nospecialize # create a path from steps @recipe function f(::Type{Val{:steppre}}, x, y, z) - plotattributes[:x] = make_steps(x, :post) - plotattributes[:y] = make_steps(y, :pre) + plotattributes[:x] = make_steps(x, :post, false) + plotattributes[:y] = make_steps(y, :pre, false) seriestype := :path # handle fillrange - plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :pre) + plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :pre, false) # create a secondary series for the markers if plotattributes[:markershape] != :none @@ -226,13 +230,38 @@ end @deps steppre path scatter # create a path from steps -@recipe function f(::Type{Val{:steppost}}, x, y, z) - plotattributes[:x] = make_steps(x, :pre) - plotattributes[:y] = make_steps(y, :post) +@recipe function f(::Type{Val{:stepmid}}, x, y, z) + plotattributes[:x] = make_steps(x, :mid, true) + plotattributes[:y] = make_steps(y, :post, true) seriestype := :path # handle fillrange - plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :post) + plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :post, true) + + # create a secondary series for the markers + if plotattributes[:markershape] != :none + @series begin + seriestype := :scatter + x := x + y := y + label := "" + primary := false + () + end + markershape := :none + end + () +end +@deps stepmid path scatter + +# create a path from steps +@recipe function f(::Type{Val{:steppost}}, x, y, z) + plotattributes[:x] = make_steps(x, :pre, false) + plotattributes[:y] = make_steps(y, :post, false) + seriestype := :path + + # handle fillrange + plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :post, false) # create a secondary series for the markers if plotattributes[:markershape] != :none diff --git a/test/runtests.jl b/test/runtests.jl index 50c6b043..4548ee47 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,12 +18,12 @@ using RecipesBase @test Plots.plotly_local_file_path[] === nothing temp = Plots.use_local_dependencies[] withenv("PLOTS_HOST_DEPENDENCY_LOCAL" => true) do - Plots.__init__() - @test Plots.plotly_local_file_path[] isa String - @test isfile(Plots.plotly_local_file_path[]) - @test Plots.use_local_dependencies[] = true - @test_nowarn Plots._init_ijulia_plotting() - end + Plots.__init__() + @test Plots.plotly_local_file_path[] isa String + @test isfile(Plots.plotly_local_file_path[]) + @test Plots.use_local_dependencies[] = true + @test_nowarn Plots._init_ijulia_plotting() +end Plots.plotly_local_file_path[] = nothing Plots.use_local_dependencies[] = temp end # testset @@ -43,7 +43,7 @@ reference_dir(args...) = joinpath(homedir(), ".julia", "dev", "PlotReferenceImag function reference_file(backend, i, version) refdir = reference_dir("Plots", string(backend)) fn = "ref$i.png" - versions = sort(VersionNumber.(readdir(refdir)), rev = true) + versions = sort(VersionNumber.(readdir(refdir)), rev=true) reffn = joinpath(refdir, string(version), fn) for v in versions @@ -107,7 +107,7 @@ const IMG_TOL = VERSION < v"1.4" && Sys.iswindows() ? 1e-1 : is_ci() ? 1e-2 : 1e @static if haskey(ENV, "APPVEYOR") @info "Skipping GR image comparison tests on AppVeyor" else - image_comparison_facts(:gr, tol=IMG_TOL, skip = Plots._backend_skips[:gr]) + image_comparison_facts(:gr, tol=IMG_TOL, skip=Plots._backend_skips[:gr]) end end @@ -156,11 +156,11 @@ end @test typeof(axis) == Plots.Axis @test Plots.discrete_value!(axis, "HI") == (0.5, 1) @test Plots.discrete_value!(axis, :yo) == (1.5, 2) - @test Plots.ignorenan_extrema(axis) == (0.5,1.5) + @test Plots.ignorenan_extrema(axis) == (0.5, 1.5) @test axis[:discrete_map] == Dict{Any,Any}(:yo => 2, "HI" => 1) - Plots.discrete_value!(axis, ["x$i" for i=1:5]) - Plots.discrete_value!(axis, ["x$i" for i=0:2]) + Plots.discrete_value!(axis, ["x$i" for i = 1:5]) + Plots.discrete_value!(axis, ["x$i" for i = 0:2]) @test Plots.ignorenan_extrema(axis) == (0.5, 7.5) end @@ -169,18 +169,27 @@ end @test unicodeplots() == Plots.UnicodePlotsBackend() @test backend() == Plots.UnicodePlotsBackend() - plots = [histogram([1, 0, 0, 0, 0, 0]), - plot([missing]), - plot([missing; 1:4]), - plot([fill(missing,10); 1:4]), - plot([1 1; 1 missing]), - plot(["a" "b"; missing "d"], [1 2; 3 4])] - for plt in plots - display(plt) + @testset "Plot" begin + plots = [histogram([1, 0, 0, 0, 0, 0]), + plot([missing]), + plot([missing; 1:4]), + plot([fill(missing, 10); 1:4]), + plot([1 1; 1 missing]), + plot(["a" "b"; missing "d"], [1 2; 3 4])] + for plt in plots + display(plt) + end + @test_nowarn plot(x -> x^2, 0, 2) + end + + @testset "Bar" begin + p = bar([3,2,1], [1,2,3]); + @test isa(p, Plots.Plot) + @test isa(display(p), Nothing) == true end - @test_nowarn plot(x->x^2,0,2) end + @testset "EmptyAnim" begin anim = @animate for i in [] end @@ -191,7 +200,7 @@ end @testset "NaN-separated Segments" begin segments(args...) = collect(iter_segments(args...)) - nan10 = fill(NaN,10) + nan10 = fill(NaN, 10) @test segments(11:20) == [1:10] @test segments([NaN]) == [] @test segments(nan10) == [] @@ -203,10 +212,10 @@ end end @testset "Utils" begin - zipped = ([(1,2)], [("a","b")], [(1,"a"),(2,"b")], - [(1,2),(3,4)], [(1,2,3),(3,4,5)], [(1,2,3,4),(3,4,5,6)], - [(1,2.0),(missing,missing)], [(1,missing),(missing,"a")], - [(missing,missing)], [(missing,missing,missing),("a","b","c")]) + zipped = ([(1, 2)], [("a", "b")], [(1, "a"),(2, "b")], + [(1, 2),(3, 4)], [(1, 2, 3),(3, 4, 5)], [(1, 2, 3, 4),(3, 4, 5, 6)], + [(1, 2.0),(missing, missing)], [(1, missing),(missing, "a")], + [(missing, missing)], [(missing, missing, missing),("a", "b", "c")]) for z in zipped @test isequal(collect(zip(Plots.unzip(z)...)), z) @test isequal(collect(zip(Plots.unzip(GeometryBasics.Point.(z))...)), z)