diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dae99f4a..2a996b021 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,10 +17,10 @@ jobs: ci: if: "!contains(github.event.head_commit.message, '[skip ci]')" env: - GKS_ENCODING: "utf8" - GKSwstype: "nul" - JULIA_CONDAPKG_BACKEND: "MicroMamba" - MPLBACKEND: "agg" + JULIA_CONDAPKG_BACKEND: MicroMamba + MPLBACKEND: agg + GKS_ENCODING: utf8 + GKSwstype: nul name: Julia ${{ matrix.version }} - ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} runs-on: ${{ matrix.os }} @@ -28,35 +28,18 @@ jobs: fail-fast: false matrix: version: - - '1.6' # LTS (minimal declared julia compat in `Project.toml`) - - '1' # latest stable + - '1' # latest stable + - '1.9' # minimal declared julia compat in `Project.toml` experimental: - false os: [ubuntu-latest, windows-latest, macos-latest] arch: [x64] include: - - os: ubuntu-latest - experimental: false - prefix: xvfb-run # julia-actions/julia-runtest/blob/master/README.md - - os: ubuntu-latest - experimental: false - prefix: xvfb-run - version: '1.7' # only test intermediate release on `ubuntu` to spare resources - - os: ubuntu-latest - experimental: false - prefix: xvfb-run - version: '1.8' # only test intermediate release on `ubuntu` to spare resources - - os: ubuntu-latest - experimental: false - prefix: xvfb-run - version: '1.9' # only test intermediate release on `ubuntu` to spare resources - os: ubuntu-latest experimental: true - prefix: xvfb-run - version: '~1.11.0-0' # upcoming julia version, next `rc` + version: '~1.11.0-0' # upcoming julia version (`alpha`, `beta` or `rc`) - os: ubuntu-latest experimental: true - prefix: xvfb-run version: 'nightly' steps: @@ -66,7 +49,7 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: | sudo apt-get -y update - sudo apt-get -y install gnuplot poppler-utils texlive-{latex-base,latex-extra,luatex} g++ + sudo apt-get -y install g++ gnuplot poppler-utils texlive-{latex-base,latex-extra,luatex} sudo fc-cache -vr - name: Set LD_PRELOAD @@ -76,69 +59,42 @@ jobs: - uses: julia-actions/setup-julia@latest with: version: ${{ matrix.version }} + - uses: julia-actions/cache@v1 - - uses: julia-actions/julia-buildpkg@latest - - name: Run upstream RecipesBase, RecipesPipeline tests - shell: julia --project=@. --color=yes {0} + - name: Develop RecipesBase, RecipesPipeline, PlotsBase, Plots + env: + JULIA_PKG_PRECOMPILE_AUTO: 0 + shell: julia --color=yes {0} run: | using Pkg - foreach(("RecipesBase", "RecipesPipeline")) do name - Pkg.develop(path=name); Pkg.test(name; coverage=true) - end + foreach(path -> Pkg.develop(; path), ("./RecipesBase", "./RecipesPipeline", "./PlotsBase", ".")) - name: Install conda based matplotlib - shell: julia --project=@. --color=yes {0} - run: | - using Pkg; Pkg.add("CondaPkg") - using CondaPkg; CondaPkg.resolve() - libgcc = if Sys.islinux() - # see discourse.julialang.org/t/glibcxx-version-not-found/82209/8 - # julia 1.8.3 is built with libstdc++.so.6.0.29, so we must restrict to this version (gcc 11.3.0, not gcc 12.2.0) - # see gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html - specs = Dict( - v"3.4.29" => ">=11.1,<12.1", - v"3.4.30" => ">=12.1,<13.1", - v"3.4.31" => ">=13.1,<14.1", - v"3.4.32" => ">=14.1,<15.1", - v"3.4.33" => ">=15.1,<16.1", - # ... keep this up-to-date with gcc 16 - )[Base.BinaryPlatforms.detect_libstdcxx_version()] - ("libgcc-ng$specs", "libstdcxx-ng$specs") - else - () - end - CondaPkg.PkgREPL.add([libgcc..., "matplotlib"]) - CondaPkg.status() - - - name: Run upstream PlotsBase tests - shell: julia --project=@. --color=yes {0} - run: | - using Pkg - foreach(("PlotsBase",)) do name - Pkg.develop(path=name); Pkg.test(name; coverage=true) - end + env: + JULIA_PKG_PRECOMPILE_AUTO: 0 + run: julia --color=yes ci/matplotlib.jl - - uses: julia-actions/julia-runtest@latest + - name: Test RecipesBase, RecipesPipeline, PlotsBase, Plots timeout-minutes: 60 - with: - prefix: ${{ matrix.prefix }} # for `xvfb-run` - - - name: Run downstream tests - if: startsWith(matrix.os, 'ubuntu') - shell: xvfb-run julia --project=@. --color=yes {0} run: | - using Pkg - foreach(("StatsPlots", "GraphRecipes")) do name - Pkg.activate(tempdir()) - foreach(path -> Pkg.develop(; path), ("RecipesBase", "RecipesPipeline", ".")) - Pkg.add(name); Pkg.test(name; coverage=true) - end + cmd=(julia --color=yes) + if [ "$RUNNER_OS" == "Linux" ]; then + cmd=(xvfb-run ${cmd[@]}) + fi + echo ${cmd[@]} + ${cmd[@]} -e ' + using Pkg + foreach(name -> Pkg.test(name; coverage=true), ("RecipesBase", "RecipesPipeline", "PlotsBase", "Plots")) + ' + - name: Test downstream packages + if: startsWith(matrix.os, 'ubuntu') + run: xvfb-run julia --color=yes ci/downstream.jl - uses: julia-actions/julia-processcoverage@latest if: startsWith(matrix.os, 'ubuntu') with: - directories: RecipesBase/src,RecipesPipeline/src,src + directories: RecipesBase/src,RecipesPipeline/src,PlotsBase/src,src - uses: codecov/codecov-action@v4 if: startsWith(matrix.os, 'ubuntu') with: diff --git a/.github/workflows/format_check.yml b/.github/workflows/format_check.yml index 951358a7d..afb15383b 100644 --- a/.github/workflows/format_check.yml +++ b/.github/workflows/format_check.yml @@ -27,7 +27,7 @@ jobs: - name: Format Julia files run: | using JuliaFormatter - format(["RecipesBase", "RecipesPipeline", "src", "test", "ext"]) + format(["RecipesBase", "RecipesPipeline", "PlotsBase", "src", "test"]) shell: julia --color=yes --compile=min -O0 {0} - name: suggester / JuliaFormatter uses: reviewdog/action-suggester@v1 diff --git a/PlotsBase/Project.toml b/PlotsBase/Project.toml index 192ef1cf0..f2df17c9a 100644 --- a/PlotsBase/Project.toml +++ b/PlotsBase/Project.toml @@ -1,6 +1,6 @@ name = "PlotsBase" uuid = "c52230a3-c5da-43a3-9e85-260fcdfdc737" -version = "1.41.0" +version = "0.1" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" @@ -19,6 +19,8 @@ NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PlotThemes = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a" PlotUtils = "995b91a9-d308-5afd-9ec6-746e21dbc043" +PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +Preferences = "21216c6a-2e73-6563-6e65-726566657250" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" @@ -87,6 +89,8 @@ PlotThemes = "2, 3" PlotUtils = "1" PlotlyJS = "0.18" PlotlyKaleido = "2.2.2" +PrecompileTools = "1" +Preferences = "1" Printf = "1" PythonPlot = "1" Random = "1" @@ -105,7 +109,7 @@ UnicodePlots = "3" UnitfulLatexify = "1" Unzip = "0.1 - 0.2" UUIDs = "1" -julia = "1.6" +julia = "1.9" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" diff --git a/PlotsBase/ext/GRExt.jl b/PlotsBase/ext/GRExt.jl index 5bc1e7f67..58211a305 100644 --- a/PlotsBase/ext/GRExt.jl +++ b/PlotsBase/ext/GRExt.jl @@ -5,41 +5,22 @@ import RecipesPipeline import NaNMath import GR -import PlotsBase.Colorbars: cbar_gradient, cbar_fill, cbar_lines - -using PlotsBase.PlotMeasures using PlotsBase.Annotations -using PlotsBase.PlotsSeries -using PlotsBase.PlotsPlots +using PlotsBase.DataSeries using PlotsBase.Colorbars using PlotsBase.Subplots using PlotsBase.Commons using PlotsBase.Arrows using PlotsBase.Shapes using PlotsBase.Colors +using PlotsBase.Plots using PlotsBase.Fonts using PlotsBase.Fonts using PlotsBase.Ticks using PlotsBase.Axes -const package_str = "GR" -const str = lowercase(package_str) -const sym = Symbol(str) - struct GRBackend <: PlotsBase.AbstractBackend end - -get_concrete_backend() = GRBackend # opposite to abstract - -function __init__() - @debug "Initializing GR backend in PlotsBase; run `gr()` to activate it." - PlotsBase._backendType[sym] = get_concrete_backend() - PlotsBase._backendSymbol[GRBackend] = sym - - push!(PlotsBase._initialized_backends, sym) -end -# Make GR know to Plots -PlotsBase.backend_name(::GRBackend) = sym -PlotsBase.backend_package_name(::GRBackend) = PlotsBase.backend_package_name(sym) +PlotsBase.@extension_static GRBackend gr const _gr_attrs = PlotsBase.merge_with_base_supported([ :annotations, @@ -196,28 +177,6 @@ const _gr_styles = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] const _gr_markers = vcat(Commons._all_markers, :pixel) const _gr_scales = [:identity, :ln, :log2, :log10] -# ----------------------------------------------------------------------------- -# Overload (dispatch) abstract `is_xxx_supported` and `supported_xxxs` methods -# defined in abstract_backend.jl - -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - v = Symbol("_$(str)_", s, "s") - quote - PlotsBase.$f1(::GRBackend, $s::Symbol) = $s in $v - PlotsBase.$f2(::GRBackend) = sort(collect($v)) - end |> eval -end - -## results in: -# PlotsBase.is_attr_supported(::GRbackend, attrname) -> Bool -# ... -# PlotsBase.supported_attrs(::GRbackend) -> ::Vector{Symbol} -# ... -# PlotsBase.supported_scales(::GRbackend) -> ::Vector{Symbol} -# ----------------------------------------------------------------------------- - PlotsBase.is_marker_supported(::GRBackend, shape::Shape) = true # https://github.com/jheinen/GR.jl - significant contributions by @jheinen @@ -473,7 +432,7 @@ end gr_inqtext(x, y, s) = gr_inqtext(x, y, string(s)) gr_inqtext(x, y, s::AbstractString) = if (occursin('\\', s) || occursin("10^{", s)) && - match(r".*\$[^\$]+?\$.*", String(s)) === nothing + match(r".*\$[^\$]+?\$.*", String(s)) ≡ nothing GR.inqtextext(x, y, s) else GR.inqtext(x, y, s) @@ -482,7 +441,7 @@ gr_inqtext(x, y, s::AbstractString) = gr_text(x, y, s) = gr_text(x, y, string(s)) gr_text(x, y, s::AbstractString) = if (occursin('\\', s) || occursin("10^{", s)) && - match(r".*\$[^\$]+?\$.*", String(s)) === nothing + match(r".*\$[^\$]+?\$.*", String(s)) ≡ nothing GR.textext(x, y, s) else GR.text(x, y, s) @@ -583,7 +542,7 @@ gr_nominal_size(s) = minimum(get_size(s)) / 500 # draw ONE Shape function gr_draw_marker(series, xi, yi, zi, clims, i, msize, strokewidth, shape::Shape) # convert to ndc coords (percentages of window) ... - xi, yi = if zi === nothing + xi, yi = if zi ≡ nothing GR.wctondc(xi, yi) else gr_w3tondc(xi, yi, zi) @@ -617,7 +576,7 @@ function gr_draw_marker(series, xi, yi, zi, clims, i, msize, strokewidth, shape: gr_set_transparency(get_markeralpha(series, i)) GR.setmarkertype(gr_markertypes[shape]) GR.setmarkersize(0.3msize / gr_nominal_size(series)) - if zi === nothing + if zi ≡ nothing GR.polymarker([xi], [yi]) else GR.polymarker3d([xi], [yi], [zi]) @@ -674,10 +633,10 @@ end function gr_viewport_from_bbox(sp::Subplot{GRBackend}, bb::BoundingBox, w, h, vp_canvas) viewport = GRViewport( - vp_canvas.xmax * (PlotsBase.left(bb) / w), - vp_canvas.xmax * (PlotsBase.right(bb) / w), - vp_canvas.ymax * (1 - PlotsBase.bottom(bb) / h), - vp_canvas.ymax * (1 - PlotsBase.top(bb) / h), + vp_canvas.xmax * (left(bb) / w), + vp_canvas.xmax * (right(bb) / w), + vp_canvas.ymax * (1 - bottom(bb) / h), + vp_canvas.ymax * (1 - top(bb) / h), ) hascolorbar(sp) && (viewport.xmax -= 0.1(1 + 0.5gr_is3d(sp))) viewport @@ -714,11 +673,12 @@ struct GRColorbar end function gr_update_colorbar!(cbar::GRColorbar, series::Series) - (style = colorbar_style(series)) === nothing && return + (style = colorbar_style(series)) ≡ nothing && return list = - style == cbar_gradient ? cbar.gradients : - style == cbar_fill ? cbar.fills : - style == cbar_lines ? cbar.lines : error("Unknown colorbar style: $style.") + style == Colorbars.cbar_gradient ? cbar.gradients : + style == Colorbars.cbar_fill ? cbar.fills : + style == Colorbars.cbar_lines ? cbar.lines : + error("Unknown colorbar style: $style.") push!(list, series) end @@ -844,18 +804,18 @@ function gr_draw_colorbar(cbar::GRColorbar, sp::Subplot, vp::GRViewport) end position(symb) = - if symb === :top || symb === :right + if symb ≡ :top || symb ≡ :right 0.95 - elseif symb === :left || symb === :bottom + elseif symb ≡ :left || symb ≡ :bottom 0.05 else 0.5 end alignment(symb) = - if symb === :top || symb === :right + if symb ≡ :top || symb ≡ :right :right - elseif symb === :left || symb === :bottom + elseif symb ≡ :left || symb ≡ :bottom :left else :center @@ -873,7 +833,7 @@ function gr_set_gradient(c) end gr_set_gradient(series::Series) = - (color = get_colorgradient(series)) !== nothing && gr_set_gradient(color) + (color = get_colorgradient(series)) ≢ nothing && gr_set_gradient(color) # this is our new display func... set up the viewport_canvas, compute bounding boxes, and display each subplot function gr_display(plt::Plot, dpi_factor = 1) @@ -973,12 +933,6 @@ function gr_get_ticks_size(ticks, rot) w, h end -function labelfunc(scale::Symbol, backend::GRBackend) - texfunc = PlotsBase.labelfunc_tex(scale) - # replace dash with \minus (U+2212) - label -> replace(texfunc(label), "-" => "−") -end - function gr_axis_height(sp, axis) GR.savestate() ticks = get_ticks(sp, axis, update = false) @@ -1011,6 +965,12 @@ function gr_axis_width(sp, axis) w end +function PlotsBase.labelfunc(scale::Symbol, backend::GRBackend) + texfunc = PlotsBase.labelfunc_tex(scale) + # replace dash with \minus (U+2212) + label -> replace(texfunc(label), "-" => "−") +end + function PlotsBase._update_min_padding!(sp::Subplot{GRBackend}) dpi = sp.plt[:thickness_scaling] width, height = sp_size = get_size(sp) @@ -1081,14 +1041,14 @@ function PlotsBase._update_min_padding!(sp::Subplot{GRBackend}) if (guide = zaxis[:guide]) != "" gr_set_font(guidefont(zaxis), sp) l = last(gr_text_size(guide)) - padding[mirrored(zaxis, :right) ? :right : :left][] += 1mm + height * l * px # NOTE: why `height` here ? + padding[mirrored(zaxis, :right) ? :right : :left][] += 1mm + height * l * px # NOTE: why `height` here ? end else # Add margin for x/y ticks & labels for (ax, tc, (a, b)) in ((xaxis, xticks, (:top, :bottom)), (yaxis, yticks, (:right, :left))) if !isempty(first(tc)) - isy = ax[:letter] === :y + isy = ax[:letter] ≡ :y gr_set_tickfont(sp, ax) ts = gr_get_ticks_size(tc, ax[:rotation]) l = 0.01 + (isy ? first(ts) : last(ts)) @@ -1123,7 +1083,7 @@ remap(x, lo, hi) = (x - lo) / (hi - lo) get_z_normalized(z, clims...) = isnan(z) ? 256 / 255 : remap(clamp(z, clims...), clims...) function gr_clims(sp, args...) - sp[:clims] === :auto || return get_clims(sp) + sp[:clims] ≡ :auto || return get_clims(sp) lo, hi = get_clims(sp, args...) if lo == hi if lo == 0 @@ -1152,8 +1112,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, vp_canvas::GRViewport) PlotsBase._update_min_padding!(sp) # the viewports for this subplot and the whole plot - vp_sp = gr_viewport_from_bbox(sp, PlotsBase.bbox(sp), w, h, vp_canvas) - vp_plt = gr_viewport_from_bbox(sp, PlotsBase.plotarea(sp), w, h, vp_canvas) + vp_sp = gr_viewport_from_bbox(sp, bbox(sp), w, h, vp_canvas) + vp_plt = gr_viewport_from_bbox(sp, plotarea(sp), w, h, vp_canvas) # update plot viewport leg = gr_get_legend_geometry(vp_plt, sp) @@ -1228,7 +1188,6 @@ function gr_add_legend(sp, leg, viewport_area) legend_rows, legend_cols = leg.column_layout if leg.w > 0 || leg.h > 0 xpos, ypos = gr_legend_pos(sp, leg, viewport_area) # position between the legend line and text (see ref(1)) - #@show vertical leg.w leg.h leg.pad leg.span leg.entries (legend_rows, legend_cols) (xpos, ypos) leg.dx leg.dy leg.textw leg.texth GR.setfillintstyle(GR.INTSTYLE_SOLID) gr_set_fillcolor(sp[:legend_background_color]) # ymax @@ -1241,7 +1200,7 @@ function gr_add_legend(sp, leg, viewport_area) GR.fillrect(xs..., ys...) # allocating white space for actual legend width here gr_set_line(1, :solid, sp[:legend_foreground_color], sp) GR.drawrect(xs..., ys...) # drawing actual legend width here - if (ttl = sp[:legend_title]) !== nothing + if (ttl = sp[:legend_title]) ≢ nothing shift = legend_rows > 1 ? 0.5(legend_cols - 1) * leg.dx : 0 # shifting title to center if multi-column gr_set_font(legendtitlefont(sp), sp) _debug[] && gr_legend_bbox(xpos, ypos, leg) @@ -1275,10 +1234,7 @@ function gr_add_legend(sp, leg, viewport_area) gr_set_line(clamped_lw, ls, lc, sp) # see github.com/JuliaPlots/Plots.jl/issues/3003 _debug[] && gr_legend_bbox(xpos, ypos, leg) - if ( - (st === :shape || series[:fillrange] !== nothing) && - series[:ribbon] === nothing - ) + if ((st ≡ :shape || series[:fillrange] ≢ nothing) && series[:ribbon] ≡ nothing) (fc = get_fillcolor(series, clims)) |> gr_set_fill gr_set_fillstyle(get_fillstyle(series, 0)) l, r = xpos + lft, xpos + rgt @@ -1294,18 +1250,18 @@ function gr_add_legend(sp, leg, viewport_area) gr_polyline(x, y, GR.fillarea) gr_set_transparency(lc, la) gr_set_line(clamped_lw, ls, lc, sp) - st === :shape && gr_polyline(x, y) + st ≡ :shape && gr_polyline(x, y) end max_markersize = Inf if st in (:path, :straightline, :path3d) max_markersize = leg.base_markersize gr_set_transparency(lc, la) - filled = series[:fillrange] !== nothing && series[:ribbon] === nothing + filled = series[:fillrange] ≢ nothing && series[:ribbon] ≡ nothing GR.polyline(xpos .+ [lft, rgt], ypos .+ (filled ? [top, top] : [0, 0])) end - if (msh = series[:markershape]) !== :none + if (msh = series[:markershape]) ≢ :none msz = max(first(series[:markersize]), 0) msw = max(first(series[:markerstrokewidth]), 0) mfac = 0.8 * lfps / (msz + 0.5 * msw + 1e-20) @@ -1340,7 +1296,7 @@ function gr_add_legend(sp, leg, viewport_area) end mirrored(ax::Axis, sym::Symbol) = - ax[:guide_position] === sym || (ax[:guide_position] === :auto && ax[:mirror]) + ax[:guide_position] ≡ sym || (ax[:guide_position] ≡ :auto && ax[:mirror]) function gr_legend_pos(sp::Subplot, leg, vp) xaxis, yaxis = sp[:xaxis], sp[:yaxis] @@ -1349,7 +1305,7 @@ function gr_legend_pos(sp::Subplot, leg, vp) if (lp = sp[:legend_position]) isa Real return gr_legend_pos(lp, leg, vp) elseif lp isa Tuple{<:Real,Symbol} - axisclearance = if lp[2] === :outer + axisclearance = if lp[2] ≡ :outer [ !ymirror * gr_axis_width(sp, yaxis), ymirror * gr_axis_width(sp, yaxis), @@ -1382,13 +1338,13 @@ function gr_legend_pos(sp::Subplot, leg, vp) vp.xmin + 0.5width(vp) - 0.5leg.w + leg.xoffset end ypos = if occursin("bottom", leg_str) - vp.ymin + if lp === :outerbottom + vp.ymin + if lp ≡ :outerbottom -leg.yoffset - leg.dy - !xmirror * gr_axis_height(sp, xaxis) else leg.yoffset + leg.h end elseif occursin("top", leg_str) # default / best - vp.ymax + if lp === :outertop + vp.ymax + if lp ≡ :outertop leg.yoffset + leg.h + xmirror * gr_axis_height(sp, xaxis) else -leg.yoffset - leg.dy @@ -1416,7 +1372,7 @@ function gr_legend_pos(theta::Real, leg, vp; axisclearance = nothing) ymin = vp.ymin - leg.yoffset - leg.dy - axisclearance[3] ymax = vp.ymax + leg.yoffset + leg.h + axisclearance[4] end - legend_pos_from_angle(theta, xmin, xcenter(vp), xmax, ymin, ycenter(vp), ymax) + PlotsBase.legend_pos_from_angle(theta, xmin, xcenter(vp), xmax, ymin, ycenter(vp), ymax) end const gr_legend_marker_to_line_factor = Ref(2.0) @@ -1426,13 +1382,13 @@ function gr_get_legend_geometry(vp, sp) textw = texth = 0.0 has_title = false nseries = 0 - if sp[:legend_position] !== :none + if sp[:legend_position] ≢ :none GR.savestate() GR.selntran(0) GR.setcharup(0, 1) GR.setscale(0) ttl = sp[:legend_title] - if (has_title = ttl !== nothing) + if (has_title = ttl ≢ nothing) gr_set_font(legendtitlefont(sp), sp) (l, r), (b, t) = extrema.(gr_inqtext(0, 0, string(ttl))) texth = t - b @@ -1523,7 +1479,7 @@ function gr_update_viewport_legend!(vp, sp, leg) xaxis, yaxis = sp[:xaxis], sp[:yaxis] xmirror = mirrored(xaxis, :top) ymirror = mirrored(yaxis, :right) - leg_str = if (lp = sp[:legend_position]) isa Tuple{<:Real,Symbol} && lp[2] === :outer + leg_str = if (lp = sp[:legend_position]) isa Tuple{<:Real,Symbol} && lp[2] ≡ :outer x, y = gr_legend_pos(sp, leg, vp) # dry run, to figure out horz = x < vp.xmin ? "left" : (x > vp.xmax ? "right" : "") vert = y < vp.ymin ? "bot" : (y > vp.ymax ? "top" : "") @@ -1544,7 +1500,7 @@ function gr_update_viewport_legend!(vp, sp, leg) vp.ymin += yoff + !xmirror * gr_axis_height(sp, xaxis) end end - if lp === :inline + if lp ≡ :inline if yaxis[:mirror] vp.xmin += leg.textw else @@ -1555,8 +1511,8 @@ function gr_update_viewport_legend!(vp, sp, leg) end gr_update_viewport_ratio!(vp, sp) = - if (ratio = get_aspect_ratio(sp)) !== :none - ratio === :equal && (ratio = 1) + if (ratio = get_aspect_ratio(sp)) ≢ :none + ratio ≡ :equal && (ratio = 1) x_min, x_max, y_min, y_max = gr_xy_axislims(sp) viewport_ratio = width(vp) / height(vp) window_ratio = (x_max - x_min) / (y_max - y_min) / ratio @@ -1635,7 +1591,7 @@ function gr_draw_axes(sp, vp) # rmin, rmax = GR.adjustrange(ignorenan_minimum(r), ignorenan_maximum(r)) rmin, rmax = axis_limits(sp, :y) gr_polaraxes(rmin, rmax, sp) - elseif sp[:framestyle] !== :none + elseif sp[:framestyle] ≢ :none foreach(letter -> gr_draw_axis(sp, letter, vp), (:x, :y)) end GR.settransparency(1.0) @@ -1713,7 +1669,7 @@ gr_draw_spine(sp, axis, segments, func = gr_polyline) = gr_draw_border(sp, axis, segments, func = gr_polyline) = if sp[:framestyle] in (:box, :semi) - intensity = sp[:framestyle] === :semi ? 0.5 : 1 + intensity = sp[:framestyle] ≡ :semi ? 0.5 : 1 GR.setclip(0) gr_set_line(intensity, :solid, axis[:foreground_color_border], sp) gr_set_transparency(axis[:foreground_color_border], intensity) @@ -1727,7 +1683,7 @@ gr_draw_ticks(sp, axis, segments, func = gr_polyline) = gr_set_line(1, :solid, axis[:foreground_color_grid], sp) gr_set_transparency( axis[:foreground_color_grid], - axis[:tick_direction] === :out ? axis[:gridalpha] : 0, + axis[:tick_direction] ≡ :out ? axis[:gridalpha] : 0, ) else gr_set_line(1, :solid, axis[:foreground_color_axis], sp) @@ -1744,14 +1700,14 @@ function gr_label_ticks(sp, letter, ticks) _, (oamin, oamax) = map(l -> axis_limits(sp, l), letters) gr_set_tickfont(sp, letter) - out_factor = ifelse(ax[:tick_direction] === :out, 1.5, 1) + out_factor = ifelse(ax[:tick_direction] ≡ :out, 1.5, 1) - isy = letter === :y + isy = letter ≡ :y x_offset = isy ? -0.015out_factor : 0 y_offset = isy ? 0 : -0.008out_factor rot = ax[:rotation] % 360 - ov = sp[:framestyle] === :origin ? 0 : xor(oax[:flip], ax[:mirror]) ? oamax : oamin + ov = sp[:framestyle] ≡ :origin ? 0 : xor(oax[:flip], ax[:mirror]) ? oamax : oamin sgn = ax[:mirror] ? -1 : 1 sgn2 = iseven(Int(floor(rot / 90))) ? -1 : 1 sgn3 = if isy @@ -1786,12 +1742,12 @@ function gr_label_ticks_3d(sp, letter, ticks) ax = sp[get_attr_symbol(letter, :axis)] ax[:showaxis] || return - isy, isz = letter .=== (:y, :z) + isy, isz = letter .≡ (:y, :z) n0, n1 = isy ? (namax, namin) : (namin, namax) gr_set_tickfont(sp, letter) - nt = sp[:framestyle] === :origin ? 0 : ax[:mirror] ? n1 : n0 - ft = sp[:framestyle] === :origin ? 0 : ax[:mirror] ? famax : famin + nt = sp[:framestyle] ≡ :origin ? 0 : ax[:mirror] ? n1 : n0 + ft = sp[:framestyle] ≡ :origin ? 0 : ax[:mirror] ? famax : famin rot = mod(ax[:rotation], 360) sgn = ax[:mirror] ? -1 : 1 @@ -1801,7 +1757,7 @@ function gr_label_ticks_3d(sp, letter, ticks) axisθ = isz ? 270 : mod(gr_get_3d_axis_angle(cvs, nt, ft, letter), 360) # issue: doesn't work with 1 tick axisϕ = mod(axisθ - 90, 360) - out_factor = ifelse(ax[:tick_direction] === :out, 1.5, 1) + out_factor = ifelse(ax[:tick_direction] ≡ :out, 1.5, 1) axis_offset = 0.012out_factor y_offset, x_offset = axis_offset .* sincosd(axisϕ) @@ -1849,26 +1805,24 @@ gr_label_axis(sp, letter, vp) = GR.savestate() guide_position = ax[:guide_position] rotation = float(ax[:guidefontrotation]) # github.com/JuliaPlots/Plots.jl/issues/3089 - if letter === :x + if letter ≡ :x # default rotation = 0. should yield GR.setcharup(0, 1) i.e. 90° xpos = xposition(vp, position(ax[:guidefonthalign])) halign = alignment(ax[:guidefonthalign]) - ypos, valign = - if guide_position === :top || (guide_position === :auto && mirror) - vp.ymax + 0.015 + (mirror ? gr_axis_height(sp, ax) : 0.015), :top - else - vp.ymin - 0.015 - (mirror ? 0.015 : gr_axis_height(sp, ax)), :bottom - end + ypos, valign = if guide_position ≡ :top || (guide_position ≡ :auto && mirror) + vp.ymax + 0.015 + (mirror ? gr_axis_height(sp, ax) : 0.015), :top + else + vp.ymin - 0.015 - (mirror ? 0.015 : gr_axis_height(sp, ax)), :bottom + end else rotation += 90 # default rotation = 0. should yield GR.setcharup(-1, 0) i.e. 180° ypos = yposition(vp, position(ax[:guidefontvalign])) halign = alignment(ax[:guidefontvalign]) - xpos, valign = - if guide_position === :right || (guide_position === :auto && mirror) - vp.xmax + 0.03 + mirror * gr_axis_width(sp, ax), :bottom - else - vp.xmin - 0.03 - !mirror * gr_axis_width(sp, ax), :top - end + xpos, valign = if guide_position ≡ :right || (guide_position ≡ :auto && mirror) + vp.xmax + 0.03 + mirror * gr_axis_width(sp, ax), :bottom + else + vp.xmin - 0.03 - !mirror * gr_axis_width(sp, ax), :top + end end gr_set_font(guidefont(ax), sp; rotation, halign, valign) gr_text(xpos, ypos, ax[:guide]) @@ -1879,7 +1833,7 @@ gr_label_axis_3d(sp, letter) = if (ax = sp[get_attr_symbol(letter, :axis)])[:guide] != "" letters = axes_letters(sp, letter) (amin, amax), (namin, namax), (famin, famax) = map(l -> axis_limits(sp, l), letters) - n0, n1 = letter === :y ? (namax, namin) : (namin, namax) + n0, n1 = letter ≡ :y ? (namax, namin) : (namin, namax) GR.savestate() gr_set_font( @@ -1896,13 +1850,13 @@ gr_label_axis_3d(sp, letter) = x, y = gr_w3tondc(sort_3d_axes(ag, ng, fg, letter)...) if letter in (:x, :y) h = gr_axis_height(sp, ax) - x_offset = letter === :x ? -h : h + x_offset = letter ≡ :x ? -h : h y_offset = -h else x_offset = -0.03 - gr_axis_width(sp, ax) y_offset = 0 end - letter === :z && GR.setcharup(-1, 0) + letter ≡ :z && GR.setcharup(-1, 0) sgn = ax[:mirror] ? -1 : 1 gr_text(x + sgn * x_offset, y + sgn * y_offset, ax[:guide]) GR.restorestate() @@ -1911,11 +1865,11 @@ gr_label_axis_3d(sp, letter) = gr_add_title(sp, vp_plt, vp_sp) = if (title = sp[:title]) != "" GR.savestate() - xpos, ypos, halign, valign = if (loc = sp[:titlelocation]) === :left + xpos, ypos, halign, valign = if (loc = sp[:titlelocation]) ≡ :left vp_plt.xmin, vp_sp.ymax, :left, :top - elseif loc === :center + elseif loc ≡ :center xcenter(vp_plt), vp_sp.ymax, :center, :top - elseif loc === :right + elseif loc ≡ :right vp_plt.xmax, vp_sp.ymax, :right, :top else xposition(vp_plt, loc[1]), @@ -1941,9 +1895,9 @@ function gr_add_series(sp, series) frng = series[:fillrange] # recompute data - if ispolar(sp) && z === nothing + if ispolar(sp) && z ≡ nothing extrema_r = gr_y_axislims(sp) - if frng !== nothing + if frng ≢ nothing _, frng = PlotsBase.convert_to_polar(x, frng, extrema_r) end x, y = PlotsBase.convert_to_polar(x, y, extrema_r) @@ -1958,34 +1912,34 @@ function gr_add_series(sp, series) # draw the series clims = gr_clims(sp, series) if (st = series[:seriestype]) in (:path, :scatter, :straightline) - if st === :straightline + if st ≡ :straightline x, y = PlotsBase.straightline_data(series) end gr_draw_segments(series, x, y, nothing, frng, clims) - if series[:markershape] !== :none + if series[:markershape] ≢ :none gr_draw_markers(series, x, y, nothing, clims) end - elseif st === :shape + elseif st ≡ :shape gr_draw_shapes(series, clims) elseif st in (:path3d, :scatter3d) gr_draw_segments(series, x, y, z, nothing, clims) - if st === :scatter3d || series[:markershape] !== :none + if st ≡ :scatter3d || series[:markershape] ≢ :none gr_draw_markers(series, x, y, z, clims) end - elseif st === :contour + elseif st ≡ :contour gr_draw_contour(series, x, y, z, clims) elseif st in (:surface, :wireframe, :mesh3d) GR.setwindow(-1, 1, -1, 1) gr_draw_surface(series, x, y, z, clims) - elseif st === :volume + elseif st ≡ :volume sp[:legend_position] = :none GR.gr3.clear() - elseif st === :heatmap + elseif st ≡ :heatmap # `z` is already transposed, so we need to reverse before passing its size. x, y = PlotsBase.heatmap_edges(x, xscale, y, yscale, reverse(size(z)), ispolar(series)) gr_draw_heatmap(series, x, y, z, clims) - elseif st === :image + elseif st ≡ :image gr_draw_image(series, x, y, z, clims) end @@ -1995,7 +1949,7 @@ function gr_add_series(sp, series) gr_text(GR.wctondc(xi, yi)..., str) end - if sp[:legend_position] === :inline && should_add_to_legend(series) + if sp[:legend_position] ≡ :inline && should_add_to_legend(series) gr_set_textcolor(plot_color(sp[:legend_font_color])) offset, halign, valign = if sp[:yaxis][:mirror] _, i = sp[:xaxis][:flip] ? findmax(x) : findmin(x) @@ -2013,10 +1967,10 @@ function gr_add_series(sp, series) end function gr_draw_segments(series, x, y, z, fillrange, clims) - (x === nothing || length(x) ≤ 1) && return - if fillrange !== nothing # prepare fill-in + (x ≡ nothing || length(x) ≤ 1) && return + if fillrange ≢ nothing # prepare fill-in GR.setfillintstyle(GR.INTSTYLE_SOLID) - fr_from, fr_to = PlotsBase.is_2tuple(fillrange) ? fillrange : (y, fillrange) + fr_from, fr_to = is_2tuple(fillrange) ? fillrange : (y, fillrange) end # draw the line(s) @@ -2024,9 +1978,9 @@ function gr_draw_segments(series, x, y, z, fillrange, clims) for segment in series_segments(series, st; check = true) i, rng = segment.attr_index, segment.range isempty(rng) && continue - is3d = st === :path3d && z !== nothing - is2d = st === :path || st === :straightline - if is2d && fillrange !== nothing + is3d = st ≡ :path3d && z ≢ nothing + is2d = st ≡ :path || st ≡ :straightline + 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))) @@ -2061,7 +2015,7 @@ function gr_draw_markers( ) isempty(x) && return GR.setfillintstyle(GR.INTSTYLE_SOLID) - (shapes = series[:markershape]) === :none && return + (shapes = series[:markershape]) ≡ :none && return for segment in series_segments(series, :scatter) rng = intersect(eachindex(IndexLinear(), x), segment.range) isempty(rng) && continue @@ -2119,7 +2073,7 @@ function gr_draw_contour(series, x, y, z, clims) gr_set_line(get_linewidth(series), get_linestyle(series), get_linecolor(series), series) gr_set_transparency(get_fillalpha(series)) h = gr_contour_levels(series, clims) - if series[:fillrange] !== nothing + if series[:fillrange] ≢ nothing GR.contourf(x, y, h, z, Int(series[:contour_labels] == true)) else black = plot_color(:black) @@ -2131,7 +2085,7 @@ end function gr_draw_surface(series, x, y, z, clims) e_kwargs = series[:extra_kwargs] - if (st = series[:seriestype]) === :surface + if (st = series[:seriestype]) ≡ :surface if ndims(x) == ndims(y) == ndims(z) == 2 GR.gr3.surface(x', y', z, GR.OPTION_3D_MESH) else @@ -2150,10 +2104,10 @@ function gr_draw_surface(series, x, y, z, clims) GR.gr3.surface(x, y, z, d_opt) end end - elseif st === :wireframe + elseif st ≡ :wireframe GR.setfillcolorind(0) GR.surface(x, y, z, get(e_kwargs, :display_option, GR.OPTION_FILLED_MESH)) - elseif st === :mesh3d + elseif st ≡ :mesh3d if series[:connections] isa AbstractVector{<:AbstractVector{Int}} # Combination of any polygon types cns = map(cns -> [length(cns), cns...], series[:connections]) @@ -2221,7 +2175,7 @@ function gr_draw_heatmap(series, x, y, z, clims) # pdf output, and also supports alpha values. # Note that drawimage draws uniformly spaced data correctly # even on log scales, where it is visually non-uniform. - _z, colors = if (scale = sp[:colorbar_scale]) === :identity + _z, colors = if (scale = sp[:colorbar_scale]) ≡ :identity z, plot_color.(get(fillgrad, z, clims), series[:fillalpha]) elseif scale ∈ _log_scales z_log, z_normalized = gr_z_normalized_log_scaled(scale, z, clims) @@ -2235,7 +2189,7 @@ function gr_draw_heatmap(series, x, y, z, clims) if something(series[:fillalpha], 1) < 1 @warn "GR: transparency not supported in non-uniform heatmaps. Alpha values ignored." end - _z, z_normalized = if (scale = sp[:colorbar_scale]) === :identity + _z, z_normalized = if (scale = sp[:colorbar_scale]) ≡ :identity z, get_z_normalized.(z, clims...) elseif scale ∈ _log_scales gr_z_normalized_log_scaled(scale, z, clims) @@ -2274,7 +2228,7 @@ for (mime, fmt) in ( "image/svg+xml" => "svg", ) @eval function PlotsBase._show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GRBackend}) - dpi_factor = $fmt == "png" ? plt[:dpi] / PlotsBase.DPI : 1 + dpi_factor = $fmt == "png" ? plt[:dpi] / DPI : 1 filepath = tempname() * "." * $fmt # workaround windows bug github.com/JuliaLang/julia/issues/46989 touch(filepath) @@ -2293,7 +2247,7 @@ for (mime, fmt) in ( end function PlotsBase._display(plt::Plot{GRBackend}) - if plt[:display_type] === :inline + if plt[:display_type] ≡ :inline filepath = tempname() * ".pdf" GR.emergencyclosegks() withenv( @@ -2319,4 +2273,4 @@ end PlotsBase.closeall(::GRBackend) = GR.emergencyclosegks() -end # module +end # module diff --git a/PlotsBase/ext/GastonExt.jl b/PlotsBase/ext/GastonExt.jl index 27fb87a50..980d3c9b4 100644 --- a/PlotsBase/ext/GastonExt.jl +++ b/PlotsBase/ext/GastonExt.jl @@ -1,39 +1,24 @@ module GastonExt import RecipesPipeline -import PlotsBase: PlotsBase, ticks_type import PlotUtils +import PlotsBase import Gaston -using PlotsBase.PlotMeasures -using PlotsBase.PlotsSeries -using PlotsBase.PlotsPlots +using PlotsBase.Annotations +using PlotsBase.DataSeries using PlotsBase.Colorbars +using PlotsBase.Surfaces using PlotsBase.Subplots using PlotsBase.Commons +using PlotsBase.Colors +using PlotsBase.Plots using PlotsBase.Ticks using PlotsBase.Fonts using PlotsBase.Axes -const package_str = "Gaston" -const str = lowercase(package_str) -const sym = Symbol(str) - struct GastonBackend <: PlotsBase.AbstractBackend end -const T = GastonBackend - -get_concrete_backend() = T # opposite to abstract - -function __init__() - @debug "Initializing $package_str backend in PlotsBase; run `$str()` to activate it." - PlotsBase._backendType[sym] = get_concrete_backend() - PlotsBase._backendSymbol[T] = sym - - push!(PlotsBase._initialized_backends, sym) -end - -PlotsBase.backend_name(::T) = sym -PlotsBase.backend_package_name(::T) = PlotsBase.backend_package_name(sym) +PlotsBase.@extension_static GastonBackend gaston const _gaston_attrs = PlotsBase.merge_with_base_supported([ :annotations, @@ -133,28 +118,6 @@ const _gaston_markers = [ const _gaston_scales = [:identity, :ln, :log2, :log10] -# ----------------------------------------------------------------------------- -# Overload (dispatch) abstract `is_xxx_supported` and `supported_xxxs` methods -# defined in abstract_backend.jl - -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - v = Symbol("_$(str)_", s, "s") - quote - PlotsBase.$f1(::T, $s::Symbol) = $s in $v - PlotsBase.$f2(::T) = sort(collect($v)) - end |> eval -end - -## results in: -# PlotsBase.is_attr_supported(::GRbackend, attrname) -> Bool -# ... -# PlotsBase.supported_attrs(::GRbackend) -> ::Vector{Symbol} -# ... -# PlotsBase.supported_scales(::GRbackend) -> ::Vector{Symbol} -# ----------------------------------------------------------------------------- - # https://github.com/mbaz/Gaston. PlotsBase.should_warn_on_unsupported(::GastonBackend) = false @@ -181,7 +144,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{GastonBackend}) foreach(series -> gaston_add_series(plt, series), plt.series_list) for sp in plt.subplots - sp === nothing && continue + sp ≡ nothing && continue for ann in sp[:annotations] x, y, val = locate_annotation(sp, ann...) sp.o.axesconf *= "; set label '$(val.str)' at $x,$y $(gaston_font(val.font))" @@ -218,14 +181,14 @@ for (mime, term) in ( @eval function PlotsBase._show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GastonBackend}) term = String($term) tmpfile = tempname() * ".$term" - if plt.o !== nothing + if plt.o ≢ nothing ret = Gaston.save(; saveopts = gaston_saveopts(plt), handle = plt.o.handle, output = tmpfile, term, ) - if ret === nothing || ret + if ret ≡ nothing || ret while !isfile(tmpfile) end # avoid race condition with read in next line write(io, read(tmpfile)) @@ -246,7 +209,7 @@ function gaston_saveopts(plt::Plot{GastonBackend}) saveopts = ["size " * join(plt[:size], ',')] # scale all plot elements to match PlotsBase.jl DPI standard - scaling = plt[:dpi] / PlotsBase.DPI + scaling = plt[:dpi] / DPI push!( saveopts, @@ -269,7 +232,7 @@ function gaston_get_subplots(n, plt_subplots, layout) nr, nc = size(layout) sps = Array{Any}(nothing, nr, nc) for r in 1:nr, c in 1:nc # NOTE: col major - sps[r, c] = if (l = layout[r, c]) isa PlotsBase.GridLayout + sps[r, c] = if (l = layout[r, c]) isa GridLayout n, sub = gaston_get_subplots(n, plt_subplots, l) size(sub) == (1, 1) ? only(sub) : sub else @@ -287,7 +250,7 @@ end function gaston_init_subplots(plt, sps) sz = nr, nc = size(sps) for c in 1:nc, r in 1:nr # NOTE: row major - if (sp = sps[r, c]) isa Subplot || sp === nothing + if (sp = sps[r, c]) isa Subplot || sp ≡ nothing gaston_init_subplot(plt, sp) else gaston_init_subplots(plt, sp) @@ -301,7 +264,7 @@ function gaston_init_subplot( plt::Plot{GastonBackend}, sp::Union{Nothing,Subplot{GastonBackend}}, ) - obj = if sp === nothing + obj = if sp ≡ nothing sp else dims = @@ -328,7 +291,7 @@ function gaston_multiplot_pos_size(layout, parent_xy_wh) # width and height (pct) are multiplicative (parent) w = layout.widths[c].value * parent_xy_wh[3] h = layout.heights[r].value * parent_xy_wh[4] - if isa(l, PlotsBase.EmptyLayout) + if isa(l, EmptyLayout) dat[r, c] = (c - 1) * w, (r - 1) * h, w, h, nothing else # previous position (origin) @@ -336,9 +299,9 @@ function gaston_multiplot_pos_size(layout, parent_xy_wh) prev_c = c > 1 ? dat[r, c - 1] : nothing prev_r isa Array && (prev_r = prev_r[end, end]) prev_c isa Array && (prev_c = prev_c[end, end]) - x = prev_c !== nothing ? prev_c[1] + prev_c[3] : parent_xy_wh[1] - y = prev_r !== nothing ? prev_r[2] + prev_r[4] : parent_xy_wh[2] - dat[r, c] = if l isa PlotsBase.GridLayout + x = prev_c ≢ nothing ? prev_c[1] + prev_c[3] : parent_xy_wh[1] + y = prev_r ≢ nothing ? prev_r[2] + prev_r[4] : parent_xy_wh[2] + dat[r, c] = if l isa GridLayout sub = gaston_multiplot_pos_size(l, (x, y, w, h)) size(sub) == (1, 1) ? only(sub) : sub else @@ -356,11 +319,10 @@ function gaston_multiplot_pos_size!(dat) gaston_multiplot_pos_size!(xy_wh_sp) elseif xy_wh_sp isa Tuple x, y, w, h, sp = xy_wh_sp - sp === nothing && continue - sp.o === nothing && continue + sp ≡ nothing && continue + sp.o ≡ nothing && continue # gnuplot screen coordinates: bottom left at 0,0 and top right at 1,1 gx, gy = x, 1 - y - h - # @show gx, gy w, h sp.o.axesconf = "set origin $gx, $gy; set size $w, $h; " * sp.o.axesconf end end @@ -369,11 +331,11 @@ end function gaston_add_series(plt::Plot{GastonBackend}, series::Series) sp = series[:subplot] - (gsp = sp.o) === nothing && return + (gsp = sp.o) ≡ nothing && return x, y, z = series[:x], series[:y], series[:z] st = series[:seriestype] curves = Gaston.Curve[] - if gsp.dims == 2 && z === nothing + if gsp.dims == 2 && 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])) @@ -385,7 +347,7 @@ function gaston_add_series(plt::Plot{GastonBackend}, series::Series) supp = nothing # supplementary column if z isa Surface z = z.surf - if st === :image + if st ≡ :image z = reverse(Float32.(Gray.(z)), dims = 1) # flip y axis nr, nc = size(z) if (ly = length(y)) == 2 && ly != nr @@ -398,9 +360,9 @@ function gaston_add_series(plt::Plot{GastonBackend}, series::Series) length(x) == size(z, 2) + 1 && (x = (x[1:(end - 1)] + x[2:end]) / 2) length(y) == size(z, 1) + 1 && (y = (y[1:(end - 1)] + y[2:end]) / 2) end - if st === :mesh3d + if st ≡ :mesh3d x, y, z = PlotsBase.mesh3d_triangles(x, y, z, series[:connections]) - elseif st === :surface + elseif st ≡ :surface if ndims(x) == ndims(y) == ndims(z) == 1 # must reinterpret 1D data for `pm3d` (points are ordered) x, y = unique(x), unique(y) @@ -461,31 +423,31 @@ function gaston_seriesconf!( fc = gaston_color(get_fillcolor(series, i), get_fillalpha(series, i)) fs = gaston_fillstyle(get_fillstyle(series, i)) lc, dt, lw = gaston_lc_ls_lw(series, clims, i) - curveconf *= if fr !== nothing # filled curves, but not filled curves with markers + curveconf *= if fr ≢ nothing # filled curves, but not filled curves with markers "w filledcurves fc $fc fs $fs border lc $lc lw $lw dt $dt,'' w lines lc $lc lw $lw dt $dt" - elseif series[:markershape] === :none # simplepath + elseif series[:markershape] ≡ :none # simplepath "w lines lc $lc dt $dt lw $lw" else pt, ps, mc = gaston_mk_ms_mc(series, clims, i) "w lp lc $mc dt $dt lw $lw pt $pt ps $ps" end - elseif st === :shape + elseif st ≡ :shape fc = gaston_color(get_fillcolor(series, i), get_fillalpha(series, i)) fs = gaston_fillstyle(get_fillstyle(series, i)) lc, = gaston_lc_ls_lw(series, clims, i) curveconf *= "w filledcurves fc $fc fs $fs border lc $lc" elseif st ∈ (:steppre, :stepmid, :steppost) - step = if st === :steppre + step = if st ≡ :steppre "fsteps" - elseif st === :stepmid + elseif st ≡ :stepmid "histeps" - elseif st === :steppost + elseif st ≡ :steppost "steps" end curveconf *= "w $step" lc, dt, lw = gaston_lc_ls_lw(series, clims, i) push!(extra_curves, "w points lc $lc dt $dt lw $lw notitle") - elseif st === :image + elseif st ≡ :image gsp.axesconf *= gaston_palette_conf(series) curveconf *= "w image pixels" elseif st ∈ (:contour, :contour3d) @@ -496,7 +458,7 @@ function gaston_seriesconf!( push!(extra_curves, "w labels notitle") end levels = collect(contour_levels(series, clims)) - if st === :contour # 2D + if st ≡ :contour # 2D gsp.axesconf *= if filled "; set view map; set palette maxcolors $(length(levels))" else @@ -507,11 +469,11 @@ function gaston_seriesconf!( elseif st ∈ (:surface, :heatmap) curveconf *= "w pm3d" gsp.axesconf *= gaston_palette_conf(series) - st === :heatmap && (gsp.axesconf *= "; set view map") + st ≡ :heatmap && (gsp.axesconf *= "; set view map") elseif st ∈ (:wireframe, :mesh3d) lc, dt, lw = gaston_lc_ls_lw(series, clims, i) curveconf *= "w lines lc $lc dt $dt lw $lw" - elseif st === :quiver + elseif st ≡ :quiver curveconf *= "w vectors filled" else @warn "PlotsBase(Gaston): $st is not implemented yet" @@ -563,7 +525,7 @@ function gaston_parse_axes_attrs( fs = sp[:framestyle] for letter in (:x, :y, :z) - (letter === :z && dims == 2) && continue + (letter ≡ :z && dims == 2) && continue axis = sp[get_attr_symbol(letter, :axis)] # NOTE: there is no `z2tics` concept in gnuplot (only 2D) @@ -576,7 +538,7 @@ function gaston_parse_axes_attrs( # guide labels guide_font = guidefont(axis) - if letter === :y && dims == 2 + if letter ≡ :y && dims == 2 # vertical by default (consistency witht other backends) guide_font = font(guide_font; rotation = guide_font.rotation + 90) end @@ -585,19 +547,19 @@ function gaston_parse_axes_attrs( "set $(letter)$(I)label '$(axis[:guide])' $(gaston_font(guide_font))", ) - logscale, base = if (scale = axis[:scale]) === :identity + logscale, base = if (scale = axis[:scale]) ≡ :identity "nologscale", "" - elseif scale === :log10 + elseif scale ≡ :log10 "logscale", "10" - elseif scale === :log2 + elseif scale ≡ :log2 "logscale", "2" - elseif scale === :ln + elseif scale ≡ :ln "logscale", "e" end push!(axesconf, "set $logscale $letter $base") # handle ticks - if axis[:showaxis] && fs !== :none + if axis[:showaxis] && fs ≢ :none if polar push!(axesconf, "set size square; unset $(letter)tics") else @@ -607,7 +569,7 @@ function gaston_parse_axes_attrs( ) # major tick locations - if axis[:ticks] !== :native + if axis[:ticks] ≢ :native if axis[:flip] hi, lo = axis_limits(sp, letter) else @@ -626,7 +588,7 @@ function gaston_parse_axes_attrs( ticks = get_ticks(sp, axis) gaston_set_ticks!(axesconf, ticks, letter, I, "", "") - if axis[:minorticks] !== :native && !no_minor_intervals(axis) + if axis[:minorticks] ≢ :native && !no_minor_intervals(axis) minor_ticks = get_minor_ticks(sp, axis, ticks) gaston_set_ticks!(axesconf, minor_ticks, letter, I, "m", "add") end @@ -636,7 +598,7 @@ function gaston_parse_axes_attrs( if fs in (:zerolines, :origin) push!(axesconf, "set $(letter)zeroaxis") end - if !axis[:showaxis] || fs === :none + if !axis[:showaxis] || fs ≡ :none push!(axesconf, "set tics scale 0", "set format x \"\"", "set format y \"\"") end @@ -646,14 +608,14 @@ function gaston_parse_axes_attrs( push!(axesconf, "set grid " * (polar ? "polar" : "m$(letter)tics")) end - if (ratio = get_aspect_ratio(sp)) !== :none + if (ratio = get_aspect_ratio(sp)) ≢ :none if dims == 2 - ratio === :equal && (ratio = -1) + ratio ≡ :equal && (ratio = -1) push!(axesconf, "set size ratio $ratio") else # ratio and square have no effect on 3D plots, # but do affect 3D projections created using set view map - if ratio === :equal + if ratio ≡ :equal push!(axesconf, "set view equal xyz") end end @@ -673,11 +635,11 @@ function gaston_parse_axes_attrs( left = gp_borders[:bottom_left_back] top = gp_borders[:bottom_right_front] right = gp_borders[:bottom_right_back] - if fs === :box + if fs ≡ :box bottom + left + top + right - elseif fs === :semi + elseif fs ≡ :semi bottom + left - elseif fs === :axes + elseif fs ≡ :axes (sp[:xaxis][:mirror] ? top : bottom) + (sp[:yaxis][:mirror] ? right : left) else 0 @@ -714,9 +676,9 @@ function gaston_parse_axes_attrs( tmin, tmax = axis_limits(sp, :x, false, false) rmin, rmax = axis_limits(sp, :y, false, false) rticks = get_ticks(sp, :y) - gaston_ticks = if (ttype = ticks_type(rticks)) === :ticks + gaston_ticks = if (ttype = PlotsBase.ticks_type(rticks)) ≡ :ticks string.(rticks) - elseif ttype === :ticks_and_labels + elseif ttype ≡ :ticks_and_labels ["'$l' $t" for (t, l) in zip(rticks...)] end push!( @@ -746,19 +708,19 @@ function gaston_fix_ticks_overflow(ticks::AbstractVector) end function gaston_set_ticks!(axesconf, ticks, letter, I, maj_min, add) - ticks === :auto && return + ticks ≡ :auto && return if ticks ∈ (:none, nothing, false) push!(axesconf, "unset $(maj_min)$(letter)tics") return end - gaston_ticks = if (ttype = ticks_type(ticks)) === :ticks + gaston_ticks = if (ttype = PlotsBase.ticks_type(ticks)) ≡ :ticks tics = gaston_fix_ticks_overflow(ticks) if maj_min == "m" map(t -> "'' $t 1", tics) # see gnuplot manual 'Mxtics' else map(string, tics) end - elseif ttype === :ticks_and_labels + elseif ttype ≡ :ticks_and_labels tics = gaston_fix_ticks_overflow(first(ticks)) labs = last(ticks) map(i -> "'$(gaston_enclose_tick_string(labs[i]))' $(tics[i])", eachindex(tics)) @@ -766,7 +728,7 @@ function gaston_set_ticks!(axesconf, ticks, letter, I, maj_min, add) @error "Gaston: invalid input for $(maj_min)$(letter)ticks: $ticks ($ttype)" nothing end - if gaston_ticks !== nothing + if gaston_ticks ≢ nothing push!(axesconf, "set $(letter)$(I)tics $add (" * join(gaston_ticks, ", ") * ")") end nothing @@ -794,7 +756,7 @@ function gaston_set_legend!(axesconf, sp, any_label) pos *= sp[:legend_column] == 1 ? "vertical" : "horizontal" push!(axesconf, "set key $pos box lw 1 opaque noautotitle") push!(axesconf, "set key $(gaston_font(legendfont(sp), rot=false, align=false))") - if sp[:legend_title] !== nothing + if sp[:legend_title] ≢ nothing # NOTE: cannot use legendtitlefont(sp) as it will override legendfont push!(axesconf, "set key title '$(sp[:legend_title])'") end @@ -814,7 +776,7 @@ gaston_valign(k) = (top = :top, vcenter = :center, bottom = :bottom)[k] # from the gnuplot docs: # - an alpha value of 0 represents a fully opaque color; i.e., "#00RRGGBB" is the same as "#RRGGBB". # - an alpha value of 255 (FF) represents full transparency -gaston_alpha(alpha) = alpha === nothing ? 0 : alpha +gaston_alpha(alpha) = alpha ≡ nothing ? 0 : alpha gaston_lc_ls_lw(series::Series, clims, i::Int) = ( gaston_color(get_linecolor(series, clims, i), get_linealpha(series, i)), @@ -847,17 +809,17 @@ gaston_palette_conf(series) = function gaston_marker(marker, alpha) # NOTE: :rtriangle, :ltriangle, :hexagon, :heptagon, :octagon seems unsupported by gnuplot filled = gaston_alpha(alpha) != 1 - marker === :none && return -1 - marker === :pixel && return 0 + marker ≡ :none && return -1 + marker ≡ :pixel && return 0 marker ∈ (:+, :cross) && return 1 marker ∈ (:x, :xcross) && return 2 - marker === :star5 && return 3 - marker === :rect && return filled ? 5 : 4 - marker === :circle && return filled ? 7 : 6 - marker === :utriangle && return filled ? 9 : 8 - marker === :dtriangle && return filled ? 11 : 10 - marker === :diamond && return filled ? 13 : 12 - marker === :pentagon && return filled ? 15 : 14 + marker ≡ :star5 && return 3 + marker ≡ :rect && return filled ? 5 : 4 + marker ≡ :circle && return filled ? 7 : 6 + marker ≡ :utriangle && return filled ? 9 : 8 + marker ≡ :dtriangle && return filled ? 11 : 10 + marker ≡ :diamond && return filled ? 13 : 12 + marker ≡ :pentagon && return filled ? 15 : 14 # @debug "PlotsBase(Gaston): unsupported marker $marker" 1 end @@ -869,18 +831,18 @@ function gaston_color(col, alpha = 0) end function gaston_linestyle(style) - style === :solid && return 1 - style === :dash && return 2 - style === :dot && return 3 - style === :dashdot && return 4 - style === :dashdotdot && return 5 + style ≡ :solid && return 1 + style ≡ :dash && return 2 + style ≡ :dot && return 3 + style ≡ :dashdot && return 4 + style ≡ :dashdotdot && return 5 1 end function gaston_enclose_tick_string(tick_string) - findfirst('^', tick_string) === nothing && return tick_string + findfirst('^', tick_string) ≡ nothing && return tick_string base, power = split(tick_string, '^') "$base^{$power}" end -end # module +end # module diff --git a/PlotsBase/ext/HDF5Ext.jl b/PlotsBase/ext/HDF5Ext.jl index 1aeaf7783..cbd39498c 100644 --- a/PlotsBase/ext/HDF5Ext.jl +++ b/PlotsBase/ext/HDF5Ext.jl @@ -2,47 +2,29 @@ module HDF5Ext import HDF5: HDF5, Group, Dataset +import RecipesPipeline: RecipesPipeline, Surface, DefaultsDict, datetimeformatter import PlotUtils: PlotUtils, Colors import PlotUtils.ColorSchemes: ColorScheme import PlotUtils.Colors: Colorant -import RecipesPipeline -import RecipesPipeline.datetimeformatter -import PlotUtils.ColorPalette, - PlotUtils.CategoricalColorGradient, PlotUtils.ContinuousColorGradient -import PlotsBase: - PlotsBase, Surface, Arrow, GridLayout, RootLayout, Font, PlotText, SeriesAnnotations -import PlotsBase: BoundingBox, Length, Plot, DefaultsDict, plot, plot! +import PlotsBase -using PlotsBase.PlotsSeries +using PlotsBase.Annotations +using PlotsBase.DataSeries using PlotsBase.Subplots using PlotsBase.Commons +using PlotsBase.Arrows using PlotsBase.Shapes +using PlotsBase.Plots +using PlotsBase.Fonts using PlotsBase.Axes import Dates -const package_str = "HDF5" -const str = lowercase(package_str) -const sym = Symbol(str) - struct HDF5Backend <: PlotsBase.AbstractBackend end -const T = HDF5Backend - -get_concrete_backend() = T # opposite to abstract +PlotsBase.@extension_static HDF5Backend hdf5 -function __init__() - @debug "Initializing $package_str backend in PlotsBase; run `$str()` to activate it." - PlotsBase._backendType[sym] = get_concrete_backend() - PlotsBase._backendSymbol[T] = sym - - push!(PlotsBase._initialized_backends, sym) -end - -PlotsBase.backend_name(::T) = sym -PlotsBase.backend_package_name(::T) = PlotsBase.backend_package_name(sym) - -const _hdf5_attr = PlotsBase.merge_with_base_supported([ +const _hdf5_attrs = PlotsBase.merge_with_base_supported([ :annotations, :legend_background_color, :background_color_inside, @@ -109,7 +91,7 @@ const _hdf5_attr = PlotsBase.merge_with_base_supported([ :dpi, :colorbar_title, ]) -const _hdf5_seriestype = [ +const _hdf5_seriestypes = [ :path, :steppre, :stepmid, @@ -127,29 +109,9 @@ const _hdf5_seriestype = [ :surface, :wireframe, ] -const _hdf5_style = [:auto, :solid, :dash, :dot, :dashdot] -const _hdf5_marker = vcat(PlotsBase.Commons._all_markers, :pixel) -const _hdf5_scale = [:identity, :ln, :log2, :log10] - -#= -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - v = Symbol("_$(str)_", s, "s") - quote - PlotsBase.$f1(::HDF5Backend, $s::Symbol) = $s in $v - PlotsBase.$f2(::HDF5Backend) = sort(collect($v)) - end |> eval -end -=# - -## results in: -# PlotsBase.is_attr_supported(::HDF5Backend, attrname) -> Bool -# ... -# PlotsBase.supported_attrs(::HDF5Backend) -> ::Vector{Symbol} -# ... -# PlotsBase.supported_scales(::HDF5Backend) -> ::Vector{Symbol} -# ----------------------------------------------------------------------------- +const _hdf5_styles = [:auto, :solid, :dash, :dot, :dashdot] +const _hdf5_markers = vcat(Commons._all_markers, :pixel) +const _hdf5_scales = [:identity, :ln, :log2, :log10] # Additional constants # Dict has problems using "Types" as keys. Initialize in "_initialize_backend": @@ -199,9 +161,9 @@ if length(HDF5PLOT_MAP_TELEM2STR) < 1 "SHAPE" => Shape, "ARROW" => Arrow, "COLORSCHEME" => ColorScheme, - "COLORPALETTE" => ColorPalette, - "CONT_COLORGRADIENT" => ContinuousColorGradient, - "CAT_COLORGRADIENT" => CategoricalColorGradient, + "COLORPALETTE" => PlotUtils.ColorPalette, + "CONT_COLORGRADIENT" => PlotUtils.ContinuousColorGradient, + "CAT_COLORGRADIENT" => PlotUtils.CategoricalColorGradient, "AXIS" => Axis, "SURFACE" => Surface, "SUBPLOT" => Subplot, @@ -215,7 +177,7 @@ end # Helper functions -h5plotpath(plotname::String) = "plots/$plotname" +h5plotpath(name::String) = "plots/$name" _hdf5_merge!(dest::AKW, src::AKW) = for (k, v) in src @@ -406,18 +368,6 @@ function _write(grp::Group, plt::Plot{HDF5Backend}) end end -function hdf5plot_write( - plt::Plot{HDF5Backend}, - path::AbstractString; - name::String = "_unnamed", -) - HDF5.h5open(path, "w") do file - HDF5.write_dataset(file, "VERSION_INFO", string(PlotsBase._current_plots_version)) - grp = HDF5.create_group(file, h5plotpath(name)) - _write(grp, plt) - end -end - # _read(): Read data, but not type information. # Types with built-in HDF5 support: @@ -540,7 +490,7 @@ function _read(grp::Group, sp::Subplot) sgrp = HDF5.open_group(listgrp, "$i") seriesinfo = _read(KW, sgrp) - plot!(sp, seriesinfo[:x], seriesinfo[:y]) # Add data & create data structures + PlotsBase.plot!(sp, seriesinfo[:x], seriesinfo[:y]) # Add data & create data structures _hdf5_merge!(sp.series_list[end].plotattributes, seriesinfo) end @@ -556,7 +506,7 @@ function _read_plot(grp::Group) n = _read_length_attrs(Vector, listgrp) # Construct new plot, +allocate subplots: - plt = plot(layout = n) + plt = PlotsBase.plot(layout = n) HDF5PLOT_PLOTREF.ref = plt # Used when reading "layout" agrp = HDF5.open_group(grp, "attr") @@ -570,47 +520,41 @@ function _read_plot(grp::Group) plt end -hdf5plot_read(path::AbstractString; name::String = "_unnamed") = - HDF5.h5open(path, "r") do file - grp = HDF5.open_group(file, h5plotpath("_unnamed")) - return _read_plot(grp) - end - # Implement PlotsBase.jl backend interface for HDF5Backend -is_marker_supported(::HDF5Backend, shape::Shape) = true +PlotsBase.is_marker_supported(::HDF5Backend, shape::Shape) = true # Create the window/figure for this backend. -function _create_backend_figure(plt::Plot{HDF5Backend}) end +function PlotsBase._create_backend_figure(plt::Plot{HDF5Backend}) end # Set up the subplot within the backend object. -function _initialize_subplot(plt::Plot{HDF5Backend}, sp::Subplot{HDF5Backend}) end +function PlotsBase._initialize_subplot(plt::Plot{HDF5Backend}, sp::Subplot{HDF5Backend}) end # Add one series to the underlying backend object. # Called once per series # NOTE: Seems to be called when user calls plot()... even if backend # plot, sp.o has not yet been constructed... -function _series_added(plt::Plot{HDF5Backend}, series::Series) end +function PlotsBase._series_added(plt::Plot{HDF5Backend}, series::Series) end # When series data is added/changed, this callback can do dynamic updates to the backend object. # note: if the backend rebuilds the plot from scratch on display, then you might not do anything here. -function _series_updated(plt::Plot{HDF5Backend}, series::Series) end +function PlotsBase._series_updated(plt::Plot{HDF5Backend}, series::Series) end # called just before updating layout bounding boxes... in case you need to prep # for the calcs -function _before_layout_calcs(plt::Plot{HDF5Backend}) end +function PlotsBase._before_layout_calcs(plt::Plot{HDF5Backend}) end # Set the (left, top, right, bottom) minimum padding around the plot area # to fit ticks, tick labels, guides, colorbars, etc. -function _update_min_padding!(sp::Subplot{HDF5Backend}) end +function PlotsBase._update_min_padding!(sp::Subplot{HDF5Backend}) end # Override this to update plot items (title, xlabel, etc), and add annotations (plotattributes[:annotations]) -function _update_plot_object(plt::Plot{HDF5Backend}) end +function PlotsBase._update_plot_object(plt::Plot{HDF5Backend}) end # ---------------------------------------------------------------- # Display/show the plot (open a GUI window, or browser page, for example). -function _display(plt::Plot{HDF5Backend}) +function PlotsBase._display(plt::Plot{HDF5Backend}) msg = "HDF5 interface does not support `display()` function." msg *= "\nUse `PlotsBase.hdf5plot_write(::String)` method to write to .HDF5 \"plot\" file instead." @warn msg @@ -618,7 +562,22 @@ function _display(plt::Plot{HDF5Backend}) end # Interface actually required to use HDF5Backend +PlotsBase.hdf5plot_write(path::AbstractString; kw...) = + PlotsBase.hdf5plot_write(current(), path; kw...) -hdf5plot_write(path::AbstractString) = hdf5plot_write(current(), path) +PlotsBase.hdf5plot_write( + plt::Plot{HDF5Backend}, + path::AbstractString; + name::String = "_unnamed", +) = + HDF5.h5open(path, "w") do file + HDF5.write_dataset(file, "VERSION_INFO", string(PlotsBase._version)) + _write(HDF5.create_group(file, h5plotpath(name)), plt) + end + +PlotsBase.hdf5plot_read(path::AbstractString; name::String = "_unnamed") = + HDF5.h5open(path, "r") do file + return _read_plot(HDF5.open_group(file, h5plotpath("_unnamed"))) + end -end # module +end # module diff --git a/PlotsBase/ext/IJuliaExt.jl b/PlotsBase/ext/IJuliaExt.jl index b94020260..47f71b049 100644 --- a/PlotsBase/ext/IJuliaExt.jl +++ b/PlotsBase/ext/IJuliaExt.jl @@ -10,54 +10,33 @@ const IJulia = function _init_ijulia_plotting() # IJulia is more stable with local file PlotsBase._use_local_plotlyjs[] = - PlotsBase._plotly_local_file_path[] === nothing ? false : + PlotsBase._plotly_local_file_path[] ≡ nothing ? false : isfile(PlotsBase._plotly_local_file_path[]) ENV["MPLBACKEND"] = "Agg" end -""" -Add extra jupyter mimetypes to display_dict based on the plot backed. - -The default is nothing, except for plotly based backends, where it -adds data for `application/vnd.plotly.v1+json` that is used in -frontends like jupyterlab and nteract. -""" -_ijulia__extra_mime_info!(plt::Plot, out::Dict) = out - -function _ijulia__extra_mime_info!(plt::Plot{PlotsBase.PlotlyJSBackend}, out::Dict) - out["application/vnd.plotly.v1+json"] = - Dict(:data => PlotsBase.plotly_series(plt), :layout => PlotsBase.plotly_layout(plt)) - out -end - -function _ijulia__extra_mime_info!(plt::Plot{PlotsBase.PlotlyBackend}, out::Dict) - out["application/vnd.plotly.v1+json"] = - Dict(:data => PlotsBase.plotly_series(plt), :layout => PlotsBase.plotly_layout(plt)) - out -end - function _ijulia_display_dict(plt::Plot) output_type = Symbol(plt.attr[:html_output_format]) - if output_type === :auto + if output_type ≡ :auto output_type = get(PlotsBase._best_html_output_type, PlotsBase.backend_name(plt.backend), :svg) end out = Dict() - if output_type === :txt + if output_type ≡ :txt mime = "text/plain" out[mime] = sprint(show, MIME(mime), plt) - elseif output_type === :png + elseif output_type ≡ :png mime = "image/png" out[mime] = base64encode(show, MIME(mime), plt) - elseif output_type === :svg + elseif output_type ≡ :svg mime = "image/svg+xml" out[mime] = sprint(show, MIME(mime), plt) - elseif output_type === :html + elseif output_type ≡ :html mime = "text/html" out[mime] = sprint(show, MIME(mime), plt) - _ijulia__extra_mime_info!(plt, out) - elseif output_type === :pdf + PlotsBase._ijulia__extra_mime_info!(plt, out) + elseif output_type ≡ :pdf mime = "application/pdf" out[mime] = base64encode(show, MIME(mime), plt) else diff --git a/PlotsBase/ext/PGFPlotsXExt.jl b/PlotsBase/ext/PGFPlotsXExt.jl index e72abe26f..a9789befc 100644 --- a/PlotsBase/ext/PGFPlotsXExt.jl +++ b/PlotsBase/ext/PGFPlotsXExt.jl @@ -1,19 +1,18 @@ module PGFPlotsXExt import PlotsBase: PlotsBase, pgfx_sanitize_string -import PlotUtils: PlotUtils, ColorGradient import LaTeXStrings: LaTeXString import Printf: @sprintf import UUIDs: uuid4 + import RecipesPipeline +import PlotUtils import PGFPlotsX import Latexify import Contour -using PlotsBase.PlotMeasures using PlotsBase.Annotations -using PlotsBase.PlotsSeries -using PlotsBase.PlotsPlots +using PlotsBase.DataSeries using PlotsBase.Colorbars using PlotsBase.Subplots using PlotsBase.Surfaces @@ -21,29 +20,13 @@ using PlotsBase.Commons using PlotsBase.Colors using PlotsBase.Shapes using PlotsBase.Arrows +using PlotsBase.Plots using PlotsBase.Fonts using PlotsBase.Ticks using PlotsBase.Axes -const package_str = "PGFPlotsX" -const str = lowercase(package_str) -const sym = Symbol(str) - struct PGFPlotsXBackend <: PlotsBase.AbstractBackend end -const T = PGFPlotsXBackend - -get_concrete_backend() = T # opposite to abstract - -function __init__() - @debug "Initializing $package_str backend in PlotsBase; run `$str()` to activate it." - PlotsBase._backendType[sym] = get_concrete_backend() - PlotsBase._backendSymbol[T] = sym - - push!(PlotsBase._initialized_backends, sym) -end - -PlotsBase.backend_name(::T) = sym -PlotsBase.backend_package_name(::T) = PlotsBase.backend_package_name(sym) +PlotsBase.@extension_static PGFPlotsXBackend pgfplotsx const _pgfplotsx_attrs = PlotsBase.merge_with_base_supported([ :annotations, @@ -216,28 +199,6 @@ PlotsBase.is_marker_supported(::PGFPlotsXBackend, shape::Shape) = true # additional constants const _pgfplotsx_series_ids = KW() -# ----------------------------------------------------------------------------- -# Overload (dispatch) abstract `is_xxx_supported` and `supported_xxxs` methods -# defined in abstract_backend.jl - -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - v = Symbol("_$(str)_", s, "s") - quote - PlotsBase.$f1(::T, $s::Symbol) = $s in $v - PlotsBase.$f2(::T) = sort(collect($v)) - end |> eval -end - -## results in: -# PlotsBase.is_attr_supported(::GRbackend, attrname) -> Bool -# ... -# PlotsBase.supported_attrs(::GRbackend) -> ::Vector{Symbol} -# ... -# PlotsBase.supported_scales(::GRbackend) -> ::Vector{Symbol} -# ----------------------------------------------------------------------------- - const Options = PGFPlotsX.Options const Table = PGFPlotsX.Table @@ -313,7 +274,7 @@ surface_to_vecs(x::AVec, y::AVec, z::AVec) = x, y, z Base.push!(pgfx_plot::PGFPlotsXPlot, item) = push!(pgfx_plot.the_plot, item) pgfx_split_extra_kw(extra) = - (get(extra, :add, nothing), filter(x -> first(x) !== :add, extra)) + (get(extra, :add, nothing), filter(x -> first(x) ≢ :add, extra)) curly(obj) = "{$(string(obj))}" @@ -332,8 +293,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) # extract extra kwargs extra_plot, extra_plot_opt = pgfx_split_extra_kw(plt[:extra_plot_kwargs]) the_plot = PGFPlotsX.TikzPicture(Options(extra_plot_opt...)) - extra_plot !== nothing && push!(the_plot, wraptuple(extra_plot)...) - bgc = plt.attr[if plt.attr[:background_color_outside] === :match + extra_plot ≢ nothing && push!(the_plot, wraptuple(extra_plot)...) + bgc = plt.attr[if plt.attr[:background_color_outside] ≡ :match :background_color else :background_color_outside @@ -353,9 +314,9 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) end for sp in plt.subplots - bb2 = PlotsBase.bbox(sp) + bb2 = bbox(sp) dx, dy = bb2.x0 - sp_w, sp_h = PlotsBase.width(bb2), PlotsBase.height(bb2) + sp_w, sp_h = width(bb2), height(bb2) if sp[:subplot_index] == plt[:plot_titleindex] x = dx + sp_w / 2 - 10mm # FIXME: get rid of magic constant y = dy + sp_h / 2 @@ -404,7 +365,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) sp_w > 0mm && push!(axis_opt, "width" => string(sp_w - (rpad + lpad))) sp_h > 0mm && push!(axis_opt, "height" => string(sp_h - (tpad + bpad))) for letter in (:x, :y, :z) - if letter !== :z || RecipesPipeline.is3d(sp) + if letter ≢ :z || RecipesPipeline.is3d(sp) pgfx_axis!(axis_opt, sp, letter) end end @@ -437,7 +398,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) if hascolorbar(sp) formatter = latex_formatter(sp[:colorbar_formatter]) cticks = curly(join(get_colorbar_ticks(sp; formatter = formatter)[1], ',')) - letter = sp[:colorbar] === :top ? :x : :y + letter = sp[:colorbar] ≡ :top ? :x : :y colorbar_style = push!( Options("$(letter)label" => sp[:colorbar_title]), @@ -446,7 +407,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) "$(letter)ticklabel style" => pgfx_get_colorbar_ticklabel_style(sp), ) - if sp[:colorbar] === :top + if sp[:colorbar] ≡ :top push!( colorbar_style, "at" => "(0.5, 1.05)", @@ -472,15 +433,12 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) push!(axis_opt, "colorbar" => "false") end if RecipesPipeline.is3d(sp) - if (ar = sp[:aspect_ratio]) !== :auto - push!( - axis_opt, - "unit vector ratio" => ar === :equal ? 1 : join(ar, ' '), - ) + if (ar = sp[:aspect_ratio]) ≢ :auto + push!(axis_opt, "unit vector ratio" => ar ≡ :equal ? 1 : join(ar, ' ')) end push!(axis_opt, "view" => tuple(sp[:camera])) end - axisf = if sp[:projection] === :polar + axisf = if sp[:projection] ≡ :polar # push!(axis_opt, "xmin" => 90) # push!(axis_opt, "xmax" => 450) PGFPlotsX.PolarAxis @@ -489,8 +447,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) end extra_sp, extra_sp_opt = pgfx_split_extra_kw(sp[:extra_kwargs]) axis = axisf(merge(axis_opt, Options(extra_sp_opt...))) - extra_sp !== nothing && push!(axis, wraptuple(extra_sp)...) - if sp[:legend_title] !== nothing + extra_sp ≢ nothing && push!(axis, wraptuple(extra_sp)...) + if sp[:legend_title] ≢ nothing legtfont = legendtitlefont(sp) leg_opt = Options( "font" => pgfx_font(legtfont.pointsize, pgfx_thickness_scaling(sp)), @@ -521,15 +479,15 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) if ( RecipesPipeline.is3d(series) || st in (:heatmap, :contour) || - (st === :quiver && opt[:z] !== nothing) + (st ≡ :quiver && opt[:z] ≢ nothing) ) PGFPlotsX.Plot3 else PGFPlotsX.Plot end if ( - series[:fillrange] !== nothing && - series[:ribbon] === nothing && + series[:fillrange] ≢ nothing && + series[:ribbon] ≡ nothing && !isfilledcontour(series) ) push!(series_opt, "area legend" => nothing) @@ -539,7 +497,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) axis.contents[end] isa PGFPlotsX.LegendEntry ? axis.contents[end - 1] : axis.contents[end] merge!(last_plot.options, Options(extra_series_opt...)) - if extra_series !== nothing + if extra_series ≢ nothing push!(axis.contents[end], wraptuple(extra_series)...) end # add series annotations @@ -589,7 +547,7 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o for (k, segment) in enumerate(segments) i, rng = segment.attr_index, segment.range segment_opt = pgfx_linestyle(opt, i) - if opt[:markershape] !== :none + if opt[:markershape] ≢ :none if (marker = _cycle(opt[:markershape], i)) isa Shape scale_factor = 0.00125 msize = opt[:markersize] * scale_factor @@ -610,10 +568,10 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o segment_opt = merge(segment_opt, pgfx_marker(opt, i)) end # add fillrange - if (sf = opt[:fillrange]) !== nothing && !isfilledcontour(series) + 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) - elseif sf isa Tuple && series[:ribbon] !== nothing + elseif sf isa Tuple && series[:ribbon] ≢ nothing for sfi in sf pgfx_fillrange_series!( axis, @@ -627,7 +585,7 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o end if ( i == 1 && - series[:subplot][:legend_position] !== :none && + series[:subplot][:legend_position] ≢ :none && pgfx_should_add_to_legend(series) ) pgfx_filllegend!(series_opt, opt) @@ -648,11 +606,11 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o isempty(opt[:label]) && push!(arrow_opt, "forget plot" => nothing) rx, ry = opt[:x][rng], opt[:y][rng] nx, ny = length(rx), length(ry) - x_arrow, y_arrow, x_path, y_path = if arrow.side === :head + x_arrow, y_arrow, x_path, y_path = if arrow.side ≡ :head rx[(nx - 1):nx], ry[(ny - 1):ny], rx[1:(nx - 1)], ry[1:(ny - 1)] - elseif arrow.side === :tail + elseif arrow.side ≡ :tail rx[2:-1:1], ry[2:-1:1], rx[2:nx], ry[2:ny] - elseif arrow.side === :both + elseif arrow.side ≡ :both rx[[2, 1, nx - 1, nx]], ry[[2, 1, ny - 1, ny]], rx[2:(nx - 1)], ry[2:(ny - 1)] end coords = Table([ @@ -670,7 +628,7 @@ function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, o end push!(axis, series_func(merge(series_opt, segment_opt), coordinates)) # fill between functions - if sf isa Tuple && series[:ribbon] === nothing + if sf isa Tuple && series[:ribbon] ≡ nothing sf1, sf2 = sf @assert sf1 == series_index "First index of the tuple has to match the current series index." push!( @@ -814,7 +772,7 @@ function pgfx_add_series!(::Val{:contour3d}, axis, series_opt, series, series_fu end function pgfx_add_series!(::Val{:quiver}, axis, series_opt, series, series_func, opt) - if (quiver = opt[:quiver]) !== nothing + if (quiver = opt[:quiver]) ≢ nothing push!( series_opt, "quiver" => Options( @@ -824,7 +782,7 @@ function pgfx_add_series!(::Val{:quiver}, axis, series_opt, series, series_func, ), ) x, y, z = opt[:x], opt[:y], opt[:z] - table = if z !== nothing + table = if z ≢ nothing push!(series_opt["quiver"], "w" => "\\thisrow{w}") pgfx_axis!(axis.options, series[:subplot], :z) [:x => x, :y => y, :z => z, :u => quiver[1], :v => quiver[2], :w => quiver[3]] @@ -867,7 +825,7 @@ function pgfx_add_series!(::Val{:xsticks}, axis, series_opt, args...) end function pgfx_add_legend!(axis, series, opt, i = 1) - if series[:subplot][:legend_position] !== :none + if series[:subplot][:legend_position] ≢ :none leg_entry = if (lab = opt[:label]) isa AVec get(lab, i, "") elseif lab isa AbstractString @@ -893,9 +851,9 @@ pgfx_series_arguments(series, opt) = surface_to_vecs(opt[:x], opt[:y], opt[:z]) elseif RecipesPipeline.is3d(st) opt[:x], opt[:y], opt[:z] - elseif st === :straightline + elseif st ≡ :straightline PlotsBase.straightline_data(series) - elseif st === :shape + elseif st ≡ :shape PlotsBase.shape_data(series) elseif ispolar(series) theta, r = opt[:x], opt[:y] @@ -998,12 +956,13 @@ function pgfx_get_legend_pos(v::Tuple{<:Real,Symbol}) "north west" "north" "north east" ] I = legend_anchor_index(s) - rect, anchor = if v[2] === :inner + rect, anchor = if v[2] ≡ :inner (0.07, 0.5, 1.0, 0.07, 0.52, 1.0), anchors[I, I] else (-0.15, 0.5, 1.05, -0.15, 0.52, 1.1), anchors[4 - I, 4 - I] end - return "at" => string(legend_pos_from_angle(v[1], rect...)), "anchor" => anchor + return "at" => string(PlotsBase.legend_pos_from_angle(v[1], rect...)), + "anchor" => anchor end function pgfx_get_legend_style(sp) @@ -1060,9 +1019,9 @@ function pgfx_get_ticklabel_style(sp, axis) ) # aligning rotated tick labels to ticks if RecipesPipeline.is3d(sp) - if axis === sp[:xaxis] + if axis ≡ sp[:xaxis] push!(opt, "anchor" => axis[:rotation] < 60 ? "north east" : "east") - elseif axis === sp[:yaxis] + elseif axis ≡ sp[:yaxis] push!(opt, "anchor" => axis[:rotation] < 45 ? "north west" : "north east") else push!( @@ -1074,7 +1033,7 @@ function pgfx_get_ticklabel_style(sp, axis) end else if mod(axis[:rotation], 90) > 0 # 0 and ±90 already look good with the default anchor - push!(opt, "anchor" => axis === sp[:xaxis] ? "north east" : "south east") + push!(opt, "anchor" => axis ≡ sp[:xaxis] ? "north east" : "south east") end end return opt @@ -1104,11 +1063,11 @@ pgfx_arrow(::Nothing) = "every arrow/.append style={-}" function pgfx_arrow(arr::Arrow, side = arr.side) components = "" arrow_head = "{Stealth[length = $(arr.headlength)pt, width = $(arr.headwidth)pt" - arr.style === :open && (arrow_head *= ", open") + arr.style ≡ :open && (arrow_head *= ", open") arrow_head *= "]}" - (side === :both || side === :tail) && (components *= arrow_head) + (side ≡ :both || side ≡ :tail) && (components *= arrow_head) components *= "-" - (side === :both || side === :head) && (components *= arrow_head) + (side ≡ :both || side ≡ :head) && (components *= arrow_head) return "every arrow/.append style={$components}" end @@ -1124,7 +1083,7 @@ end pgfx_colormap(cl::PlotUtils.AbstractColorList) = pgfx_colormap(PlotUtils.color_list(cl)) pgfx_colormap(v::Vector{<:Colorant}) = join(map(c -> @sprintf("rgb=(%.8f,%.8f,%.8f)", red(c), green(c), blue(c)), v), '\n') -pgfx_colormap(cg::ColorGradient) = join( +pgfx_colormap(cg::PlotUtils.ColorGradient) = join( map(1:length(cg)) do i @sprintf( "rgb(%.8f)=(%.8f,%.8f,%.8f)", @@ -1141,7 +1100,7 @@ pgfx_framestyle(style::Symbol) = if style in (:box, :axes, :origin, :zerolines, :grid, :none) style else - default_style = style === :semi ? :box : :axes + default_style = style ≡ :semi ? :box : :axes @warn "Framestyle :$style is not (yet) supported by the PGFPlotsX backend. :$default_style was chosen instead." default_style end @@ -1151,7 +1110,7 @@ pgfx_thickness_scaling(sp::Subplot) = pgfx_thickness_scaling(sp.plt) pgfx_thickness_scaling(series) = pgfx_thickness_scaling(series[:subplot]) function pgfx_fillstyle(plotattributes, i = 1) - if (a = get_fillalpha(plotattributes, i)) === nothing + if (a = get_fillalpha(plotattributes, i)) ≡ nothing a = alpha(single_color(get_fillcolor(plotattributes, i))) end Options("fill" => get_fillcolor(plotattributes, i), "fill opacity" => a) @@ -1167,7 +1126,7 @@ function pgfx_linestyle(linewidth::Real, color, α = 1, linestyle = :solid) ) end -pgfx_legend_col(s::Symbol) = s === :horizontal ? -1 : 1 +pgfx_legend_col(s::Symbol) = s ≡ :horizontal ? -1 : 1 pgfx_legend_col(n) = n function pgfx_linestyle(plotattributes, i = 1) @@ -1233,11 +1192,11 @@ function pgfx_marker(plotattributes, i = 1) pgfx_thickness_scaling(plotattributes) * 0.75 * _cycle(plotattributes[:markerstrokewidth], i), - "rotate" => if shape === :dtriangle + "rotate" => if shape ≡ :dtriangle 180 - elseif shape === :rtriangle + elseif shape ≡ :rtriangle 270 - elseif shape === :ltriangle + elseif shape ≡ :ltriangle 90 else 0 @@ -1290,7 +1249,7 @@ function pgfx_fillrange_series!(axis, series, series_func, i, fillrange, rng) opt[:x][rng], opt[:y][rng], opt[:z][rng] elseif ispolar(series) rad2deg.(opt[:x][rng]), opt[:y][rng] - elseif series[:seriestype] === :straightline + elseif series[:seriestype] ≡ :straightline PlotsBase.straightline_data(series) else opt[:x][rng], opt[:y][rng] @@ -1335,7 +1294,7 @@ function pgfx_sanitize_plot!(plt) end for subplot in plt.subplots for (key, value) in subplot.attr - if key === :annotations && subplot.attr[:annotations] !== nothing + if key ≡ :annotations && subplot.attr[:annotations] ≢ nothing old_ann = subplot.attr[key] for i in eachindex(old_ann) # [1:end-1] is a tuple of coordinates, [end] - text @@ -1355,8 +1314,8 @@ function pgfx_sanitize_plot!(plt) end for series in plt.series_list for (key, value) in series.plotattributes - if key === :series_annotations && - series.plotattributes[:series_annotations] !== nothing + if key ≡ :series_annotations && + series.plotattributes[:series_annotations] ≢ nothing old_ann = series.plotattributes[key].strs for i in eachindex(old_ann) series.plotattributes[key].strs[i] = pgfx_sanitize_string(old_ann[i]) @@ -1413,9 +1372,9 @@ function pgfx_axis!(opt::Options, sp::Subplot, letter) framestyle = pgfx_framestyle(sp[:framestyle] == false ? :none : sp[:framestyle]) # axis label position - labelpos = if letter === :x + labelpos = if letter ≡ :x pgfx_get_xguide_pos(axis[:guide_position]) - elseif letter === :y + elseif letter ≡ :y pgfx_get_yguide_pos(axis[:guide_position]) else "" @@ -1443,38 +1402,38 @@ function pgfx_axis!(opt::Options, sp::Subplot, letter) scale = axis[:scale] if (is_log_scale = scale in (:ln, :log2, :log10)) push!(opt, "$(letter)mode" => "log") - scale === :ln || push!(opt, "log basis $letter" => "$(scale === :log2 ? 2 : 10)") + scale ≡ :ln || push!(opt, "log basis $letter" => "$(scale ≡ :log2 ? 2 : 10)") end # ticks on or off - if axis[:ticks] in (nothing, false, :none) || framestyle === :none + if axis[:ticks] in (nothing, false, :none) || framestyle ≡ :none push!(opt, "$(letter)majorticks" => "false") elseif framestyle in (:grid, :zerolines) push!(opt, "$letter tick style" => Options("draw" => "none")) end # grid on or off - push!(opt, "$(letter)majorgrids" => string(axis[:grid] && framestyle !== :none)) + push!(opt, "$(letter)majorgrids" => string(axis[:grid] && framestyle ≢ :none)) # limits - lims = if ispolar(sp) && letter === :x + lims = if ispolar(sp) && letter ≡ :x rad2deg.(axis_limits(sp, :x)) else axis_limits(sp, letter) end push!(opt, "$(letter)min" => lims[1], "$(letter)max" => lims[2]) - if axis[:ticks] ∉ (nothing, false, :none, :native) && framestyle !== :none + if axis[:ticks] ∉ (nothing, false, :none, :native) && framestyle ≢ :none vals, labs = ticks = get_ticks(sp, axis, formatter = latex_formatter(axis[:formatter])) # pgfplot ignores ticks with angles below `90` when `xmin = 90`, so shift values - tick_values = if ispolar(sp) && letter === :x + tick_values = if ispolar(sp) && letter ≡ :x vcat(rad2deg.(vals[3:end]), 360, 405) else vals end tick_labels = if axis[:showaxis] - if is_log_scale && axis[:ticks] === :auto + if is_log_scale && axis[:ticks] ≡ :auto labels = wrap_power_labels(labs) if (lab = first(labels)) isa LaTeXString || pgfx_is_inline_math(lab) join(labels, ',') @@ -1482,7 +1441,7 @@ function pgfx_axis!(opt::Options, sp::Subplot, letter) "\\(" * join(labels, "\\),\\(") * "\\)" end else - labels = if ispolar(sp) && letter === :x + labels = if ispolar(sp) && letter ≡ :x vcat(labs[3:end], "0", "45") else labs @@ -1496,7 +1455,7 @@ function pgfx_axis!(opt::Options, sp::Subplot, letter) opt, "$(letter)ticklabels" => curly(tick_labels), "$(letter)tick" => curly(join(tick_values, ',')), - if (tick_dir = axis[:tick_direction]) === :none || axis[:showaxis] === false + if (tick_dir = axis[:tick_direction]) ≡ :none || axis[:showaxis] ≡ false "$(letter)tick style" => "draw=none" else "$(letter)tick align" => "$(tick_dir)side" @@ -1516,8 +1475,8 @@ function pgfx_axis!(opt::Options, sp::Subplot, letter) # Hence, we hack around with extra ticks. # Unfortunately this conflicts with `:zerolines` framestyle hack. # So minor ticks are not working with `:zerolines`. - if (minor_ticks = get_minor_ticks(sp, axis, ticks)) !== nothing - if ispolar(sp) && letter === :x + if (minor_ticks = get_minor_ticks(sp, axis, ticks)) ≢ nothing + if ispolar(sp) && letter ≡ :x minor_ticks = vcat(rad2deg.(minor_ticks[3:end]), 360, 405) end push!( @@ -1547,7 +1506,7 @@ function pgfx_axis!(opt::Options, sp::Subplot, letter) push!( opt, # the * after line disables the arrow at the axis "axis $letter line$(axis[:draw_arrow] ? "" : "*")" => - (axis[:mirror] ? "right" : framestyle === :axes ? "left" : "middle"), + (axis[:mirror] ? "right" : framestyle ≡ :axes ? "left" : "middle"), ) end @@ -1556,7 +1515,7 @@ function pgfx_axis!(opt::Options, sp::Subplot, letter) push!(opt, "$(letter)ticklabel pos" => (axis[:mirror] ? "right" : "left")) end - if framestyle === :zerolines + if framestyle ≡ :zerolines gs = pgfx_linestyle(pgfx_thickness_scaling(sp), axis[:foreground_color_border], 1) push!( opt, @@ -1636,4 +1595,4 @@ function PlotsBase._display(plt::Plot{PGFPlotsXBackend}) display(PGFPlotsX.PGFPlotsXDisplay(), plt.o.the_plot) end -end # module +end # module diff --git a/PlotsBase/ext/PlotlyJSExt.jl b/PlotsBase/ext/PlotlyJSExt.jl index 27f06a4c1..3b3acea36 100644 --- a/PlotsBase/ext/PlotlyJSExt.jl +++ b/PlotsBase/ext/PlotlyJSExt.jl @@ -1,33 +1,14 @@ module PlotlyJSExt -import PlotsBase: PlotsBase, Plot, isijulia -using PlotsBase.PlotsPlots +import PlotsBase: PlotsBase, Plot using PlotsBase.Commons using PlotsBase.Plotly +using PlotsBase.Plots import PlotlyJS: PlotlyJS, WebIO -# unrolling the old # init_backend macro by hand case by case -# this is not a macro for the backend maintainers and explicit control -const package_str = "PlotlyJS" -const str = lowercase(package_str) -const sym = Symbol(str) - struct PlotlyJSBackend <: PlotsBase.AbstractBackend end -const T = PlotlyJSBackend - -get_concrete_backend() = T # opposite to abstract - -function __init__() - @debug "Initializing $package_str backend in PlotsBase; run `$str()` to activate it." - PlotsBase._backendType[sym] = get_concrete_backend() - PlotsBase._backendSymbol[T] = sym - - push!(PlotsBase._initialized_backends, sym) -end - -PlotsBase.backend_name(::T) = sym -PlotsBase.backend_package_name(::T) = PlotsBase.backend_package_name(sym) +PlotsBase.@extension_static PlotlyJSBackend plotlyjs const _plotlyjs_attrs = PlotsBase.Plotly._plotly_attrs const _plotlyjs_seriestypes = PlotsBase.Plotly._plotly_seriestypes @@ -35,31 +16,6 @@ const _plotlyjs_styles = PlotsBase.Plotly._plotly_styles const _plotlyjs_markers = PlotsBase.Plotly._plotly_markers const _plotlyjs_scales = PlotsBase.Plotly._plotly_scales -# ----------------------------------------------------------------------------- -# Overload (dispatch) abstract `is_xxx_supported` and `supported_xxxs` methods -# defined in abstract_backend.jl - -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - v = Symbol("_$(str)_", s, "s") - quote - PlotsBase.$f1(::T, $s::Symbol) = $s in $v - PlotsBase.$f2(::T) = sort(collect($v)) - end |> eval -end - -## results in: -# PlotsBase.is_attr_supported(::GRbackend, attrname) -> Bool -# ... -# PlotsBase.supported_attrs(::GRbackend) -> ::Vector{Symbol} -# ... -# PlotsBase.supported_scales(::GRbackend) -> ::Vector{Symbol} -# ----------------------------------------------------------------------------- -# https://github.com/JuliaPlots/PlotlyJS.jl - -# ------------------------------------------------------------------------------ - function plotlyjs_syncplot(plt::Plot{PlotlyJSBackend}) plt[:overwrite_figure] && PlotsBase.closeall() plt.o = PlotlyJS.plot() @@ -112,4 +68,10 @@ PlotsBase.closeall(::PlotlyJSBackend) = Base.showable(::MIME"application/prs.juno.plotpane+html", plt::Plot{PlotlyJSBackend}) = true -end # module +function PlotsBase._ijulia__extra_mime_info!(plt::Plot{PlotlyJSBackend}, out::Dict) + out["application/vnd.plotly.v1+json"] = + Dict(:data => plotly_series(plt), :layout => plotly_layout(plt)) + out +end + +end # module diff --git a/PlotsBase/ext/PlotlyKaleidoExt.jl b/PlotsBase/ext/PlotlyKaleidoExt.jl index bff882427..1a86add86 100644 --- a/PlotsBase/ext/PlotlyKaleidoExt.jl +++ b/PlotsBase/ext/PlotlyKaleidoExt.jl @@ -27,4 +27,4 @@ for (mime, fmt) in ( ) end -end # module +end # module diff --git a/PlotsBase/ext/PythonPlotExt.jl b/PlotsBase/ext/PythonPlotExt.jl index 5b37e0bd6..90a5298b2 100644 --- a/PlotsBase/ext/PythonPlotExt.jl +++ b/PlotsBase/ext/PythonPlotExt.jl @@ -4,57 +4,65 @@ import RecipesPipeline import PythonPlot import NaNMath +const PythonCall = PythonPlot.PythonCall +const pyisnone = + isdefined(PythonCall, :pyisnone) ? PythonCall.pyisnone : PythonCall.Core.pyisnone + +const mpl_toolkits = PythonCall.pynew() +const numpy = PythonCall.pynew() +const mpl = PythonCall.pynew() + +using PlotUtils + import PlotsBase -import PlotsBase.PlotUtils: PlotUtils, ColorGradient, plot_color, color_list, cgrad -import PlotsBase.Commons: Commons, single_color -using PlotsBase.PlotMeasures using PlotsBase.Annotations -using PlotsBase.PlotsSeries -using PlotsBase.PlotsPlots +using PlotsBase.DataSeries using PlotsBase.Colorbars +using PlotsBase.Surfaces using PlotsBase.Subplots using PlotsBase.Commons using PlotsBase.Colors using PlotsBase.Arrows using PlotsBase.Shapes +using PlotsBase.Plots using PlotsBase.Fonts using PlotsBase.Ticks using PlotsBase.Axes -const package_str = "PythonPlot" -const str = lowercase(package_str) -const sym = Symbol(str) - struct PythonPlotBackend <: PlotsBase.AbstractBackend end -const T = PythonPlotBackend - -get_concrete_backend() = T - -function __init__() - @debug "Initializing $package_str backend in PlotsBase; run `$str()` to activate it." - PlotsBase._backendType[sym] = get_concrete_backend() - PlotsBase._backendSymbol[T] = sym - - push!(PlotsBase._initialized_backends, sym) +function PlotsBase.extension_init(::PythonPlotBackend) if PythonPlot.version < v"3.4" @warn """You are using Matplotlib $(PythonPlot.version), which is no longer officially supported by the Plots community. To ensure smooth PlotsBase.jl integration update your Matplotlib library to a version ≥ 3.4.0 """ end - PythonCall.pycopy!(mpl, PythonCall.pyimport("matplotlib")) PythonCall.pycopy!(mpl_toolkits, PythonCall.pyimport("mpl_toolkits")) PythonCall.pycopy!(numpy, PythonCall.pyimport("numpy")) PythonCall.pyimport("mpl_toolkits.axes_grid1") numpy.seterr(invalid = "ignore") - PythonPlot.ioff() # we don't want every command to update the figure + PythonPlot.ioff() # we don't want every command to update the figure + + # WARNING: matplotlib uses a reverse convention: `labeltop` instead of `toplabel` + for keyword in (:linthresh, :base, :label) + Commons.new_attr_dict!(keyword) + for letter in (:x, :y, :z, Symbol(), :top, :bottom, :left, :right) + Commons.set_attr_symbol!(keyword, string(letter)) + end + end + + # problem: github.com/tbreloff/Plots.jl/issues/308 + # solution: hack from @stevengj: github.com/JuliaPy/PyPlot.jl/pull/223#issuecomment-229747768 + let otherdisplays = + splice!(Base.Multimedia.displays, 2:length(Base.Multimedia.displays)) + append!(Base.Multimedia.displays, otherdisplays) + end end -PlotsBase.backend_name(::T) = sym -PlotsBase.backend_package_name(::T) = PlotsBase.backend_package_name(sym) +PlotsBase.@extension_static PythonPlotBackend pythonplot const _pythonplot_attrs = PlotsBase.merge_with_base_supported([ :annotations, @@ -190,59 +198,12 @@ const _pythonplot_styles = [:auto, :solid, :dash, :dot, :dashdot] const _pythonplot_markers = vcat(Commons._all_markers, :pixel) const _pythonplot_scales = [:identity, :ln, :log2, :log10] -# ----------------------------------------------------------------------------- -# Overload (dispatch) abstract `is_xxx_supported` and `supported_xxxs` methods -# defined in abstract_backend.jl - -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - v = Symbol("_$(str)_", s, "s") - quote - PlotsBase.$f1(::T, $s::Symbol) = $s in $v - PlotsBase.$f2(::T) = sort(collect($v)) - end |> eval -end - -## results in: -# PlotsBase.is_attr_supported(::GRbackend, attrname) -> Bool -# ... -# PlotsBase.supported_attrs(::GRbackend) -> ::Vector{Symbol} -# ... -# PlotsBase.supported_scales(::GRbackend) -> ::Vector{Symbol} -# ----------------------------------------------------------------------------- - # github.com/stevengj/PythonPlot.jl -const PythonCall = PythonPlot.PythonCall -const mpl = PythonCall.pynew() # PythonCall.pyimport("matplotlib") -const mpl_toolkits = PythonCall.pynew() # PythonCall.pyimport("mpl_toolkits") -const numpy = PythonCall.pynew() # PythonCall.pyimport("numpy") - -const pyisnone = if isdefined(PythonCall, :pyisnone) - PythonCall.pyisnone -else - PythonCall.Core.pyisnone -end - PlotsBase.is_marker_supported(::PythonPlotBackend, shape::Shape) = true -# problem: github.com/tbreloff/Plots.jl/issues/308 -# solution: hack from @stevengj: github.com/JuliaPy/PyPlot.jl/pull/223#issuecomment-229747768 -let otherdisplays = splice!(Base.Multimedia.displays, 2:length(Base.Multimedia.displays)) - append!(Base.Multimedia.displays, otherdisplays) -end - -for k in (:linthresh, :base, :label) - # add PythonPlot specific symbols to cache - Commons._attrsymbolcache[k] = Dict{Symbol,Symbol}() - for letter in (:x, :y, :z, Symbol(), :top, :bottom, :left, :right) - Commons._attrsymbolcache[k][letter] = Symbol(k, letter) - end -end - _py_handle_surface(v) = v -_py_handle_surface(z::PlotsBase.Surface) = z.surf +_py_handle_surface(z::Surface) = z.surf _py_color(s) = _py_color(parse(Colorant, string(s))) _py_color(c::Colorant) = [red(c), green(c), blue(c), alpha(c)] # NOTE: returning a tuple fails `PythonPlot` @@ -274,11 +235,11 @@ _py_shading(c, z) = mpl.colors.LightSource(270, 45).shade( # get the style (solid, dashed, etc) function _py_linestyle(seriestype::Symbol, linestyle::Symbol) - seriestype === :none && return " " - linestyle === :solid && return "-" - linestyle === :dash && return "--" - linestyle === :dot && return ":" - linestyle === :dashdot && return "-." + seriestype ≡ :none && return " " + linestyle ≡ :solid && return "-" + linestyle ≡ :dash && return "--" + linestyle ≡ :dot && return ":" + linestyle ≡ :dashdot && return "-." @warn "Unknown linestyle $linestyle" "-" end @@ -297,23 +258,24 @@ end # get the marker shape function _py_marker(marker::Symbol) - marker === :none && return " " - marker === :circle && return "o" - marker === :rect && return "s" - marker === :diamond && return "D" - marker === :utriangle && return "^" - marker === :dtriangle && return "v" - marker === :+ && return "+" - marker === :x && return "x" - marker === :star5 && return "*" - marker === :pentagon && return "p" - marker === :hexagon && return "h" - marker === :octagon && return "8" - marker === :pixel && return "," - marker === :hline && return "_" - marker === :vline && return "|" - haskey(_shapes, marker) && return _py_marker(_shapes[marker]) - + marker ≡ :none && return " " + marker ≡ :circle && return "o" + marker ≡ :rect && return "s" + marker ≡ :diamond && return "D" + marker ≡ :utriangle && return "^" + marker ≡ :dtriangle && return "v" + marker ≡ :+ && return "+" + marker ≡ :x && return "x" + marker ≡ :star5 && return "*" + marker ≡ :pentagon && return "p" + marker ≡ :hexagon && return "h" + marker ≡ :octagon && return "8" + marker ≡ :pixel && return "," + marker ≡ :hline && return "_" + marker ≡ :vline && return "|" + let _shapes = Shapes._shapes + haskey(_shapes, marker) && return _py_marker(_shapes[marker]) + end @warn "Unknown marker $marker" "o" end @@ -331,16 +293,16 @@ function _py_marker(marker::AbstractString) end function _py_stepstyle(seriestype::Symbol) - seriestype === :steppost && return "steps-post" - seriestype === :stepmid && return "steps-mid" - seriestype === :steppre && return "steps-pre" + seriestype ≡ :steppost && return "steps-post" + seriestype ≡ :stepmid && return "steps-mid" + seriestype ≡ :steppre && return "steps-pre" "default" end function _py_fillstepstyle(seriestype::Symbol) - seriestype === :steppost && return "post" - seriestype === :stepmid && return "mid" - seriestype === :steppre && return "pre" + seriestype ≡ :steppost && return "post" + seriestype ≡ :stepmid && return "mid" + seriestype ≡ :steppre && return "pre" nothing end @@ -367,17 +329,17 @@ get_locator_and_formatter(vals::AVec) = mpl.ticker.FixedLocator(eachindex(vals)), mpl.ticker.FixedFormatter(vals) labelfunc(scale::Symbol, backend::PythonPlotBackend) = - PythonPlot.LaTeXStrings.latexstring ∘ PlotsBase.labelfunc_tex(scale) + PythonPlot.LaTeXStrings.latexstring ∘ labelfunc_tex(scale) _py_mask_nans(z) = PythonPlot.pycall(numpy.ma.masked_invalid, z) # --------------------------------------------------------------------------- function fix_xy_lengths!(plt::Plot{PythonPlotBackend}, series::Series) - if (x = series[:x]) !== nothing + if (x = series[:x]) ≢ nothing y = series[:y] nx, ny = length(x), length(y) - if !(get(series.plotattributes, :z, nothing) isa PlotsBase.Surface || nx == ny) + if !(get(series.plotattributes, :z, nothing) isa Surface || nx == ny) if nx < ny series[:x] = map(i -> Float64(x[mod1(i, nx)]), 1:ny) else @@ -426,11 +388,11 @@ _py_renderer(fig) = _py_canvas(fig).get_renderer() _py_drawfig(fig) = fig.draw(_py_renderer(fig)) # `get_points` returns a numpy array in the form [x0 y0; x1 y1] coords (origin is bottom-left (0, 0)!) -_py_extents(obj) = PythonCall.PyArray(obj.get_window_extent().get_points()) +_py_extents(obj) = PythonPlot.PyArray(obj.get_window_extent().get_points()) # see cjdoris.github.io/PythonCall.jl/stable/conversion-to-julia/#py2jl-conversion -to_vec(x) = PythonCall.pyconvert(Vector, x) -to_str(x) = PythonCall.pyconvert(String, x) +to_vec(x) = PythonPlot.pyconvert(Vector, x) +to_str(x) = PythonPlot.pyconvert(String, x) # compute a bounding box (with origin top-left), however PythonPlot gives coords with origin bottom-left function _py_bbox(obj) @@ -438,12 +400,12 @@ function _py_bbox(obj) fl, fr, fb, ft = bb = _py_extents(obj.get_figure()) l, r, b, t = ex = _py_extents(obj) # @show obj bb ex - x0, y0, width, height = l * px, (ft - t) * px, (r - l) * px, (t - b) * px - # @show width height - PlotsBase.BoundingBox(x0, y0, width, height) + x0, y0, w, h = l * px, (ft - t) * px, (r - l) * px, (t - b) * px + # @show w h + BoundingBox(x0, y0, w, h) end -_py_bbox(::Nothing) = PlotsBase.BoundingBox(0mm, 0mm) +_py_bbox(::Nothing) = BoundingBox(0mm, 0mm) # get the bounding box of the union of the objects function _py_bbox(v::AVec) @@ -493,7 +455,7 @@ _py_thickness_scale(plt::Plot{PythonPlotBackend}, ptsz) = ptsz * plt[:thickness_ # Create the window/figure for this backend. function PlotsBase._create_backend_figure(plt::Plot{PythonPlotBackend}) - w, h = map(s -> PlotMeasures.px2inch(s * plt[:dpi] / PlotsBase.DPI), plt[:size]) + w, h = map(s -> Commons.px2inch(s * plt[:dpi] / DPI), plt[:size]) # reuse the current figure? plt[:overwrite_figure] ? PythonPlot.gcf() : PythonPlot.figure() end @@ -541,9 +503,9 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) # ax = getAxis(plt, series) x, y, z = (_py_handle_surface(series[letter]) for letter in (:x, :y, :z)) - if st === :straightline + if st ≡ :straightline x, y = PlotsBase.straightline_data(series) - elseif st === :shape + elseif st ≡ :shape x, y = PlotsBase.shape_data(series) end @@ -562,7 +524,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) vmin, vmax = clims = get_clims(sp, series) # Dict to store extra kwargs - extrakw = if st === :wireframe || st === :hexbin + extrakw = if st ≡ :wireframe || st ≡ :hexbin # vmin, vmax cause an error for wireframe plot # We are not supporting clims for hexbin as calculation of bins is not trivial KW() @@ -576,8 +538,8 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) # pass in an integer value as an arg, but a levels list as a keyword arg levels = series[:levels] - levelargs = PlotsBase.isscalar(levels) ? levels : () - PlotsBase.isvector(levels) && (extrakw[:levels] = levels) + levelargs = isscalar(levels) ? levels : () + isvector(levels) && (extrakw[:levels] = levels) # add custom frame shapes to markershape? series_annotations_shapes!(series, :xy) @@ -612,7 +574,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) ) |> push_h end - if (a = series[:arrow]) !== nothing && !RecipesPipeline.is3d(st) # TODO: handle 3d later + if (a = series[:arrow]) ≢ nothing && !RecipesPipeline.is3d(st) # TODO: handle 3d later if typeof(a) != Arrow @warn "Unexpected type for arrow: $(typeof(a))" else @@ -639,18 +601,16 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) end # add markers ? - if series[:markershape] !== :none && st ∈ _py_marker_series + if series[:markershape] ≢ :none && st ∈ _py_marker_series for segment in series_segments(series, :scatter) i, rng = segment.attr_index, segment.range - args = if st === :bar - x[rng], y[rng] - end + args = x[rng], y[rng] RecipesPipeline.is3d(sp) && (args = (args..., z[rng])) ax.scatter( args...; zorder = zorder + 0.5, - marker = _py_marker(PlotsBase._cycle(series[:markershape], i)), - s = _py_thickness_scale(plt, PlotsBase._cycle(series[:markersize], i)) .^ 2, + marker = _py_marker(_cycle(series[:markershape], i)), + s = _py_thickness_scale(plt, _cycle(series[:markersize], i)) .^ 2, facecolors = _py_color( get_markercolor(series, i, cbar_scale), get_markeralpha(series, i), @@ -666,7 +626,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) end end - if st === :shape + if st ≡ :shape for segment in series_segments(series) i, rng = segment.attr_index, segment.range if length(rng) > 1 @@ -713,7 +673,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) end end end - elseif st === :image + elseif st ≡ :image x, y = series[:x], series[:y] xmin, xmax = ignorenan_extrema(x) ymin, ymax = ignorenan_extrema(y) @@ -729,7 +689,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) else z # hopefully it's in a data format that will "just work" with imshow end - aspect = if get_aspect_ratio(sp) === :equal + aspect = if get_aspect_ratio(sp) ≡ :equal "equal" else "auto" @@ -743,7 +703,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) zorder, aspect, ) |> push_h - elseif st === :heatmap + elseif st ≡ :heatmap x, y = PlotsBase.heatmap_edges(x, xaxis[:scale], y, yaxis[:scale], size(z)) expand_extrema!(xaxis, x) expand_extrema!(yaxis, y) @@ -758,7 +718,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) label, extrakw..., ) |> push_h - elseif st === :mesh3d + elseif st ≡ :mesh3d cns = series[:connections] polygons = if cns isa AbstractVector{<:AbstractVector{<:Integer}} # Combination of any polygon types @@ -798,7 +758,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) ) |> ax.add_collection3d |> push_h - elseif st === :hexbin + elseif st ≡ :hexbin sekw = series[:extra_kwargs] extrakw[:mincnt] = get(sekw, :mincnt, nothing) extrakw[:edgecolors] = get(sekw, :edgecolors, edgecolor) @@ -806,7 +766,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) x, y; C = series[:weights], - gridsize = series[:bins] === :auto ? 100 : series[:bins], # 100 is the default value + gridsize = series[:bins] ≡ :auto ? 100 : series[:bins], # 100 is the default value cmap = _py_fillcolormap(series), # applies to the pcolorfast object linewidths, zorder, @@ -815,7 +775,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) extrakw..., ) |> push_h elseif st ∈ (:contour, :contour3d) - if st === :contour3d + if st ≡ :contour3d extrakw[:extend3d] = true if !ismatrix(x) || !ismatrix(y) x, y = repeat(x', length(y), 1), repeat(y, 1, length(x)) @@ -838,10 +798,10 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) extrakw..., ) ) |> push_h - series[:contour_labels] === true && ax.clabel(handle, handle.levels) + series[:contour_labels] ≡ true && ax.clabel(handle, handle.levels) # contour fills - series[:fillrange] !== nothing && + series[:fillrange] ≢ nothing && ax.contourf( x, y, @@ -857,8 +817,8 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) if !ismatrix(x) || !ismatrix(y) x, y = repeat(x', length(y), 1), repeat(y, 1, length(x)) end - if st === :surface - if series[:fill_z] !== nothing + if st ≡ :surface + if series[:fill_z] ≢ nothing # the surface colors are different than z-value extrakw[:facecolors] = _py_shading(series[:fillcolor], _py_handle_surface(series[:fill_z])) @@ -918,15 +878,15 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series) # handleSmooth(plt, ax, series, series[:smooth]) # handle area filling - if (fillrange = series[:fillrange]) !== nothing && st !== :contour + if (fillrange = series[:fillrange]) ≢ nothing && st ≢ :contour for segment in series_segments(series) i, rng = segment.attr_index, segment.range f, dim1, dim2 = :fill_between, x[rng], y[rng] n = length(dim1) args = if typeof(fillrange) <: Union{Real,AVec} - dim1, PlotsBase._cycle(fillrange, rng), dim2 - elseif PlotsBase.is_2tuple(fillrange) - dim1, PlotsBase._cycle(fillrange[1], rng), PlotsBase._cycle(fillrange[2], rng) + dim1, _cycle(fillrange, rng), dim2 + elseif is_2tuple(fillrange) + dim1, _cycle(fillrange[1], rng), _cycle(fillrange[2], rng) end la = get_linealpha(series, i) @@ -965,9 +925,9 @@ _py_set_lims(ax, sp::Subplot, axis::Axis) = end function _py_set_ticks(sp, ax, ticks, letter) - ticks === :auto && return + ticks ≡ :auto && return axis = getproperty(ax, get_attr_symbol(letter, :axis)) - if ticks === :none || ticks === nothing || ticks == false + if ticks ≡ :none || ticks ≡ nothing || ticks == false kw = KW() for dir in (:top, :bottom, :left, :right) kw[dir] = kw[get_attr_symbol(:label, dir)] = false @@ -976,9 +936,9 @@ function _py_set_ticks(sp, ax, ticks, letter) return end - tick_values, tick_labels = if (ttype = PlotsBase.ticks_type(ticks)) === :ticks + tick_values, tick_labels = if (ttype = ticks_type(ticks)) ≡ :ticks ticks, [] - elseif ttype === :ticks_and_labels + elseif ttype ≡ :ticks_and_labels ticks else error("Invalid input for $(letter)ticks: $ticks") @@ -1004,12 +964,12 @@ end function _py_set_scale(ax, sp::Subplot, scale::Symbol, letter::Symbol) scale ∈ PlotsBase.supported_scales() || return @warn "Unhandled scale value in PythonPlot: $scale" - scl, kw = if scale === :identity + scl, kw = if scale ≡ :identity "linear", KW() else "symlog", KW( - get_attr_symbol(:base, Symbol()) => _log_scale_bases[scale], + get_attr_symbol(:base, Symbol()) => Commons._log_scale_bases[scale], get_attr_symbol(:linthresh, Symbol()) => NaNMath.max( 1e-16, _py_compute_axis_minval(sp, sp[get_attr_symbol(letter, :axis)]), @@ -1029,8 +989,8 @@ _py_set_spine_color(spines::Dict, color) = function _py_set_axis_colors(sp, ax, a::Axis) _py_set_spine_color(ax.spines, _py_color(a[:foreground_color_border])) - axissym = get_attr_symbol(a[:letter], :axis) - if hasproperty(ax, axissym) + axis_sym = get_attr_symbol(a[:letter], :axis) + if hasproperty(ax, axis_sym) tickcolor = sp[:framestyle] ∈ (:zerolines, :grid) ? _py_color(plot_color(a[:foreground_color_grid], a[:gridalpha])) : @@ -1041,7 +1001,7 @@ function _py_set_axis_colors(sp, ax, a::Axis) colors = tickcolor, labelcolor = _py_color(a[:tickfontcolor]), ) - getproperty(ax, axissym).label.set_color(_py_color(a[:guidefontcolor])) + getproperty(ax, axis_sym).label.set_color(_py_color(a[:guidefontcolor])) end end @@ -1054,7 +1014,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) w, h = plt[:size] fig = plt.o fig.clear() - fig.set_size_inches(w / PlotsBase.DPI, h / PlotsBase.DPI, forward = true) + fig.set_size_inches(w / DPI, h / DPI, forward = true) fig.set_facecolor(_py_color(plt[:background_color_outside])) fig.set_dpi(plt[:dpi]) @@ -1069,7 +1029,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) # update subplots for sp in plt.subplots - (ax = sp.o) === nothing && continue + (ax = sp.o) ≡ nothing && continue xaxis, yaxis = sp[:xaxis], sp[:yaxis] # add the annotations @@ -1104,7 +1064,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) kw = KW() handle = if !isempty(sp[:zaxis][:discrete_values]) && - cbar_series[:seriestype] === :heatmap + cbar_series[:seriestype] ≡ :heatmap kw[:ticks], kw[:format] = get_locator_and_formatter(sp[:zaxis][:discrete_values]) # kw[:values] = eachindex(sp[:zaxis][:discrete_values]) @@ -1112,20 +1072,20 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) kw[:boundaries] = vcat(0, kw[:values] + 0.5) cbar_series[:serieshandle][end] elseif any( - cbar_series[attr] !== nothing for attr in (:line_z, :fill_z, :marker_z) + cbar_series[attr] ≢ nothing for attr in (:line_z, :fill_z, :marker_z) ) cmin, cmax = get_clims(sp) - norm = if cbar_scale === :identity + norm = if cbar_scale ≡ :identity mpl.colors.Normalize(vmin = cmin, vmax = cmax) else mpl.colors.LogNorm(vmin = cmin, vmax = cmax) end cmap = nothing for func in (_py_linecolormap, _py_fillcolormap, _py_markercolormap) - (cmap = func(cbar_series)) === nothing || break + (cmap = func(cbar_series)) ≡ nothing || break end c_map = mpl.cm.ScalarMappable(; cmap, norm) - c_map.set_array(PythonCall.pylist([])) + c_map.set_array(PythonPlot.pylist([])) c_map else cbar_series[:serieshandle][end] @@ -1140,22 +1100,22 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) else # divider approach works only with 2d plots divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax) - pos, pad, orientation = if cb_sym === :left + pos, pad, orientation = if cb_sym ≡ :left cb_sym, "5%", "vertical" - elseif cb_sym === :top + elseif cb_sym ≡ :top cb_sym, "2.5%", "horizontal" - elseif cb_sym === :bottom + elseif cb_sym ≡ :bottom cb_sym, "5%", "horizontal" else # :right or :best :right, "2.5%", "vertical" end # Reasonable value works most of the usecases cax = divider.append_axes(string(pos); size = "5%", label, pad) - if cb_sym === :left + if cb_sym ≡ :left cax.yaxis.set_ticks_position("left") - elseif cb_sym === :right + elseif cb_sym ≡ :right cax.yaxis.set_ticks_position("right") - elseif cb_sym === :top + elseif cb_sym ≡ :top cax.xaxis.set_ticks_position("top") else # :bottom or :best cax.xaxis.set_ticks_position("bottom") @@ -1172,7 +1132,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) ) # cbar.formatter.set_useOffset(false) # this for some reason does not work, must be a pyplot bug, instead this is a workaround: - cbar_scale === :identity && cbar.formatter.set_powerlimits((-Inf, Inf)) + cbar_scale ≡ :identity && cbar.formatter.set_powerlimits((-Inf, Inf)) cbar.update_ticks() ticks = get_colorbar_ticks(sp) @@ -1182,8 +1142,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) yaxis, cbar.ax.yaxis, :y # colorbar inherits from y axis end _py_set_scale(cbar.ax, sp, sp[:colorbar_scale], ticks_letter) - sp[:colorbar_ticks] === :native || - _py_set_ticks(sp, cbar.ax, ticks, ticks_letter) + sp[:colorbar_ticks] ≡ :native || _py_set_ticks(sp, cbar.ax, ticks, ticks_letter) for lab in cbar_axis.get_ticklabels() lab.set_fontsize(_py_thickness_scale(plt, sp[:colorbar_tickfontsize])) @@ -1197,9 +1156,9 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) # Adjust thickness of the cbar ticks intensity = 0.5 cbar_axis.set_tick_params( - direction = axis[:tick_direction] === :out ? "out" : "in", + direction = axis[:tick_direction] ≡ :out ? "out" : "in", width = _py_thickness_scale(plt, intensity), - length = axis[:tick_direction] === :none ? 0 : + length = axis[:tick_direction] ≡ :none ? 0 : 5_py_thickness_scale(plt, intensity), ) @@ -1217,7 +1176,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) end # Then set visible some of them - if framestyle === :semi + if framestyle ≡ :semi intensity = 0.5 pyspine = getproperty(ax.spines, yaxis[:mirror] ? "left" : "right") @@ -1227,7 +1186,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) pyspine = getproperty(ax.spines, xaxis[:mirror] ? "bottom" : "top") pyspine.set_linewidth(_py_thickness_scale(plt, intensity)) pyspine.set_alpha(intensity) - elseif framestyle === :box + elseif framestyle ≡ :box ax.tick_params(top = true) # Add ticks too ax.tick_params(right = true) # Add ticks too elseif framestyle ∈ (:axes, :origin) @@ -1235,13 +1194,13 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) (xaxis[:mirror] ? "bottom" : "top", yaxis[:mirror] ? "left" : "right") getproperty(ax.spines, loc).set_visible(false) end - if framestyle === :origin + if framestyle ≡ :origin ax.spines.bottom.set_position("zero") ax.spines.left.set_position("zero") end elseif framestyle ∈ (:grid, :none, :zerolines) _py_hide_spines(ax) - if framestyle === :zerolines + if framestyle ≡ :zerolines ax.axhline( y = 0, color = _py_color(xaxis[:foreground_color_axis]), @@ -1257,37 +1216,37 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) if xaxis[:mirror] ax.xaxis.set_label_position("top") # the guides - framestyle === :box || ax.xaxis.tick_top() + framestyle ≡ :box || ax.xaxis.tick_top() end if yaxis[:mirror] ax.yaxis.set_label_position("right") # the guides - framestyle === :box || ax.yaxis.tick_right() + framestyle ≡ :box || ax.yaxis.tick_right() end end # axis attributes for letter in (:x, :y, :z) - axissym = get_attr_symbol(letter, :axis) - hasproperty(ax, axissym) || continue - axis = sp[axissym] - pyaxis = getproperty(ax, axissym) + axis_sym = get_attr_symbol(letter, :axis) + hasproperty(ax, axis_sym) || continue + axis = sp[axis_sym] + pyaxis = getproperty(ax, axis_sym) - if axis[:guide_position] !== :auto && letter !== :z + if axis[:guide_position] ≢ :auto && letter ≢ :z pyaxis.set_label_position(string(axis[:guide_position])) end _py_set_scale(ax, sp, axis) _py_set_lims(ax, sp, axis) - (ispolar(sp) && letter === :y) && ax.set_rlabel_position(90) - ticks = framestyle === :none ? nothing : get_ticks(sp, axis) + (ispolar(sp) && letter ≡ :y) && ax.set_rlabel_position(90) + ticks = framestyle ≡ :none ? nothing : get_ticks(sp, axis) - has_major_ticks = ticks !== :none && ticks !== nothing && ticks !== false - has_major_ticks &= if (ttype = PlotsBase.ticks_type(ticks)) === :ticks + has_major_ticks = ticks ≢ :none && ticks ≢ nothing && ticks ≢ false + has_major_ticks &= if (ttype = ticks_type(ticks)) ≡ :ticks length(ticks) > 0 - elseif ttype === :ticks_and_labels + elseif ttype ≡ :ticks_and_labels tcs, labs = ticks - if framestyle === :origin + if framestyle ≡ :origin # don't show the 0 tick label for the origin framestyle labs[tcs .== 0] .= "" end @@ -1300,7 +1259,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) intensity = 0.5 # this value corresponds to scaling of other grid elements length_factor = 6 # arbitrary factor (closest to mpl examples) - if axis[:ticks] === :native # it is easier to reset than to account for this + if axis[:ticks] ≡ :native # it is easier to reset than to account for this _py_set_lims(ax, sp, axis) pyaxis.set_major_locator(mpl.ticker.AutoLocator()) pyaxis.set_major_formatter(mpl.ticker.ScalarFormatter()) @@ -1322,9 +1281,9 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) _py_set_ticks(sp, ax, ticks, letter) pyaxis.set_tick_params( - direction = axis[:tick_direction] === :out ? "out" : "in", + direction = axis[:tick_direction] ≡ :out ? "out" : "in", width = _py_thickness_scale(plt, intensity), - length = axis[:tick_direction] === :none ? 0 : + length = axis[:tick_direction] ≡ :none ? 0 : length_factor * _py_thickness_scale(plt, intensity), ) else @@ -1341,7 +1300,7 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) RecipesPipeline.is3d(sp) && pyaxis.set_rotate_label(false) axis[:flip] && getproperty(ax, Symbol(:invert_, letter, :axis))() - axis[:guidefontrotation] + if letter === :y && !RecipesPipeline.is3d(sp) + axis[:guidefontrotation] + if letter ≡ :y && !RecipesPipeline.is3d(sp) 90 else 0 @@ -1364,18 +1323,18 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) if !no_minor_intervals(axis) && has_major_ticks ax.minorticks_on() n_minor_intervals = num_minor_intervals(axis) - if (scale = axis[:scale]) === :identity + if (scale = axis[:scale]) ≡ :identity mpl.ticker.AutoMinorLocator(n_minor_intervals) else mpl.ticker.LogLocator( - base = _log_scale_bases[scale], + base = Commons._log_scale_bases[scale], subs = 1:n_minor_intervals, ) end |> pyaxis.set_minor_locator pyaxis.set_tick_params( which = "minor", - direction = axis[:tick_direction] === :out ? "out" : "in", - length = axis[:tick_direction] === :none ? 0 : + direction = axis[:tick_direction] ≡ :out ? "out" : "in", + length = axis[:tick_direction] ≡ :none ? 0 : 0.5length_factor * _py_thickness_scale(plt, intensity), ) end @@ -1412,11 +1371,11 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) end # aspect ratio - if (ratio = get_aspect_ratio(sp)) !== :none + if (ratio = get_aspect_ratio(sp)) ≢ :none if RecipesPipeline.is3d(sp) - if ratio === :auto + if ratio ≡ :auto nothing - elseif ratio === :equal + elseif ratio ≡ :equal ax.set_box_aspect((1, 1, 1)) else ax.set_box_aspect(ratio) @@ -1452,18 +1411,17 @@ function PlotsBase._before_layout_calcs(plt::Plot{PythonPlotBackend}) end expand_padding!(padding, bb, plotbb) = - if PlotsBase.ispositive(PlotsBase.width(bb)) && - PlotsBase.ispositive(PlotsBase.height(bb)) - padding[1] = max(padding[1], PlotsBase.left(plotbb) - PlotsBase.left(bb)) - padding[2] = max(padding[2], PlotsBase.top(plotbb) - PlotsBase.top(bb)) - padding[3] = max(padding[3], PlotsBase.right(bb) - PlotsBase.right(plotbb)) - padding[4] = max(padding[4], PlotsBase.bottom(bb) - PlotsBase.bottom(plotbb)) + if ispositive(width(bb)) && ispositive(height(bb)) + padding[1] = max(padding[1], left(plotbb) - left(bb)) + padding[2] = max(padding[2], top(plotbb) - top(bb)) + padding[3] = max(padding[3], right(bb) - right(plotbb)) + padding[4] = max(padding[4], bottom(bb) - bottom(plotbb)) end -# Set the (left, top, right, bottom) minimum padding around the plot area +# set the (left, top, right, bottom) minimum padding around the plot area # to fit ticks, tick labels, guides, colorbars, etc. function PlotsBase._update_min_padding!(sp::Subplot{PythonPlotBackend}) - (ax = sp.o) === nothing && return sp.minpad + (ax = sp.o) ≡ nothing && return sp.minpad plotbb = _py_bbox(ax) # TODO: this should initialize to the margin from sp.attr @@ -1500,7 +1458,7 @@ function PlotsBase._update_min_padding!(sp::Subplot{PythonPlotBackend}) # add ∈ the user-specified margin padding .+= [sp[:left_margin], sp[:top_margin], sp[:right_margin], sp[:bottom_margin]] - sp.minpad = Tuple((PlotsBase.DPI / sp.plt[:dpi]) .* padding) + sp.minpad = Tuple((DPI / sp.plt[:dpi]) .* padding) end # ----------------------------------------------------------------- @@ -1512,8 +1470,8 @@ _py_add_annotations(sp::Subplot{PythonPlotBackend}, x, y, val::PlotText) = sp.o. val.str, xy = (x, y), size = _py_thickness_scale(sp.plt, val.font.pointsize), - horizontalalignment = val.font.halign === :hcenter ? "center" : string(val.font.halign), - verticalalignment = val.font.valign === :vcenter ? "center" : string(val.font.valign), + horizontalalignment = val.font.halign ≡ :hcenter ? "center" : string(val.font.halign), + verticalalignment = val.font.valign ≡ :vcenter ? "center" : string(val.font.valign), color = _py_color(val.font.color), rotation = val.font.rotation, family = val.font.family, @@ -1527,8 +1485,8 @@ _py_add_annotations(sp::Subplot{PythonPlotBackend}, x, y, z, val::PlotText) = sp z, val.str; size = _py_thickness_scale(sp.plt, val.font.pointsize), - horizontalalignment = val.font.halign === :hcenter ? "center" : string(val.font.halign), - verticalalignment = val.font.valign === :vcenter ? "center" : string(val.font.valign), + horizontalalignment = val.font.halign ≡ :hcenter ? "center" : string(val.font.halign), + verticalalignment = val.font.valign ≡ :vcenter ? "center" : string(val.font.valign), color = _py_color(val.font.color), rotation = val.font.rotation, family = val.font.family, @@ -1540,16 +1498,12 @@ _py_add_annotations(sp::Subplot{PythonPlotBackend}, x, y, z, val::PlotText) = sp _py_legend_pos(pos::Tuple{S,T}) where {S<:Real,T<:Real} = "lower left" function _py_legend_pos(pos::Tuple{<:Real,Symbol}) - s, c = sincosd(pos[1]) .* (pos[2] === :outer ? -1 : 1) + s, c = sincosd(pos[1]) .* (pos[2] ≡ :outer ? -1 : 1) yanchors = "lower", "center", "upper" xanchors = "left", "center", "right" - join( - [ - yanchors[PlotsBase.legend_anchor_index(s)], - xanchors[PlotsBase.legend_anchor_index(c)], - ], - ' ', - ) + let lac = PlotsBase.legend_anchor_index + join([yanchors[lac(s)], xanchors[lac(c)]], ' ') + end end # legend_pos_from_angle(theta, xmin, xcenter, xmax, ymin, ycenter, ymax) @@ -1558,7 +1512,7 @@ _py_legend_bbox(pos::Tuple{<:Real,Symbol}) = _py_legend_bbox(pos) = pos function _py_add_legend(plt::Plot, sp::Subplot, ax) - (leg = sp[:legend_position]) === :none && return + (leg = sp[:legend_position]) ≡ :none && return # gotta do this to ensure both axes are included labels, handles = [], [] @@ -1570,7 +1524,7 @@ function _py_add_legend(plt::Plot, sp::Subplot, ax) clims = get_clims(sp, series) nseries += 1 # add a line/marker and a label - if series[:seriestype] === :shape || series[:fillrange] !== nothing + if series[:seriestype] ≡ :shape || series[:fillrange] ≢ nothing lc = get_linecolor(series, clims) fc = get_fillcolor(series, clims) la = get_linealpha(series) @@ -1620,7 +1574,7 @@ function _py_add_legend(plt::Plot, sp::Subplot, ax) solid_joinstyle = "miter", dash_capstyle = "butt", dash_joinstyle = "miter", - marker = _py_marker(PlotsBase._cycle(series[:markershape], 1)), + marker = _py_marker(_cycle(series[:markershape], 1)), markersize = _py_thickness_scale(plt, 0.8sp[:legend_font_pointsize]), markeredgecolor = _py_color( single_color(get_markerstrokecolor(series)), @@ -1671,7 +1625,7 @@ function _py_add_legend(plt::Plot, sp::Subplot, ax) ) leg.get_frame().set_linewidth(_py_thickness_scale(plt, 1)) leg.set_zorder(1_000) - if sp[:legend_title] !== nothing + if sp[:legend_title] ≢ nothing leg.set_title(string(sp[:legend_title])) PythonPlot.setp( leg.get_title(), @@ -1698,27 +1652,24 @@ end # position the subplot in the backend. function PlotsBase._update_plot_object(plt::Plot{PythonPlotBackend}) for sp in plt.subplots - (ax = sp.o) === nothing && return + (ax = sp.o) ≡ nothing && return figw, figh = sp.plt[:size] .* px # ax.set_position signature: `[left, bottom, width, height]` - PlotsBase.bbox_to_pcts(sp.plotarea, figw, figh) |> ax.set_position + bbox_to_pcts(sp.plotarea, figw, figh) |> ax.set_position if haskey(sp.attr, :cbar_ax) && RecipesPipeline.is3d(sp) # 2D plots are completely handled by axis dividers bb = sp.attr[:cbar_bbox] # this is the bounding box of just the colors of the colorbar (not labels) pad = 2mm - cb_bbox = PlotsBase.BoundingBox( - PlotsBase.right(sp.bbox) - 2PlotsBase.width(bb) - 2pad, # x0 - PlotsBase.top(sp.bbox) + pad, # y0 - PlotsBase.width(bb), # width - PlotsBase.height(sp.bbox) - 2pad, # height + cb_bbox = BoundingBox( + right(sp.bbox) - 2width(bb) - 2pad, # x0 + top(sp.bbox) + pad, # y0 + width(bb), # width + height(sp.bbox) - 2pad, # height ) - get( - sp[:extra_kwargs], - "3d_colorbar_axis", - PlotsBase.bbox_to_pcts(cb_bbox, figw, figh), - ) |> sp.attr[:cbar_ax].set_position + get(sp[:extra_kwargs], "3d_colorbar_axis", bbox_to_pcts(cb_bbox, figw, figh)) |> + sp.attr[:cbar_ax].set_position end end PythonPlot.draw() @@ -1757,4 +1708,4 @@ end PlotsBase.closeall(::PythonPlotBackend) = PythonPlot.close("all") -end # module +end # module diff --git a/PlotsBase/ext/UnicodePlotsExt.jl b/PlotsBase/ext/UnicodePlotsExt.jl index b517e2176..9d306cc4e 100644 --- a/PlotsBase/ext/UnicodePlotsExt.jl +++ b/PlotsBase/ext/UnicodePlotsExt.jl @@ -4,38 +4,21 @@ import PlotsBase: PlotsBase, texmath2unicode import RecipesPipeline import UnicodePlots -using PlotsBase.PlotMeasures -using PlotsBase.PlotsSeries using PlotsBase.Annotations -using PlotsBase.PlotsPlots +using PlotsBase.DataSeries using PlotsBase.Colorbars using PlotsBase.Subplots using PlotsBase.Commons using PlotsBase.Shapes using PlotsBase.Arrows using PlotsBase.Colors +using PlotsBase.Plots using PlotsBase.Fonts using PlotsBase.Ticks using PlotsBase.Axes -const package_str = "UnicodePlots" -const str = lowercase(package_str) -const sym = Symbol(str) - struct UnicodePlotsBackend <: PlotsBase.AbstractBackend end -const T = UnicodePlotsBackend - -get_concrete_backend() = UnicodePlotsBackend # opposite to abstract - -function __init__() - @debug "Initializing $package_str backend in PlotsBase; run `$str()` to activate it." - PlotsBase._backendType[sym] = get_concrete_backend() - PlotsBase._backendSymbol[T] = sym - - push!(PlotsBase._initialized_backends, sym) -end -PlotsBase.backend_name(::UnicodePlotsBackend) = sym -PlotsBase.backend_package_name(::UnicodePlotsBackend) = PlotsBase.backend_package_name(sym) +PlotsBase.@extension_static UnicodePlotsBackend unicodeplots const _unicodeplots_attrs = PlotsBase.merge_with_base_supported([ :annotations, @@ -112,27 +95,6 @@ const _unicodeplots_markers = [ :x, ] const _unicodeplots_scales = [:identity, :ln, :log2, :log10] -# ----------------------------------------------------------------------------- -# Overload (dispatch) abstract `is_xxx_supported` and `supported_xxxs` methods -# defined in abstract_backend.jl - -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - v = Symbol("_$(str)_", s, "s") - quote - PlotsBase.$f1(::UnicodePlotsBackend, $s::Symbol) = $s in $v - PlotsBase.$f2(::UnicodePlotsBackend) = sort(collect($v)) - end |> eval -end - -## results in: -# PlotsBase.is_attr_supported(::GRbackend, attrname) -> Bool -# ... -# PlotsBase.supported_attrs(::GRbackend) -> ::Vector{Symbol} -# ... -# PlotsBase.supported_scales(::GRbackend) -> ::Vector{Symbol} -# ----------------------------------------------------------------------------- # https://github.com/JuliaPlots/UnicodePlots.jl @@ -148,7 +110,7 @@ const _canvas_map = ( PlotsBase.should_warn_on_unsupported(::UnicodePlotsBackend) = false -function PlotsBase._before_layout_calcs(plt::PlotsBase.Plot{UnicodePlotsBackend}) +function PlotsBase._before_layout_calcs(plt::Plot{UnicodePlotsBackend}) plt.o = UnicodePlots.Plot[] up_width = UnicodePlots.DEFAULT_WIDTH[] up_height = UnicodePlots.DEFAULT_HEIGHT[] @@ -412,11 +374,7 @@ end # ------------------------------------------------------------------------------------------ -function PlotsBase._show( - io::IO, - ::MIME"image/png", - plt::PlotsBase.Plot{UnicodePlotsBackend}, -) +function PlotsBase._show(io::IO, ::MIME"image/png", plt::Plot{UnicodePlotsBackend}) applicable(UnicodePlots.save_image, io) || "PlotsBase(UnicodePlots): saving to `.png` requires `import FreeType, FileIO`" |> ArgumentError |> @@ -428,7 +386,7 @@ function PlotsBase._show( imgs = [] sps = 0 for r in 1:nr, c in 1:nc - if (l = plt.layout[r, c]) isa PlotsBase.GridLayout && size(l) != (1, 1) + if (l = plt.layout[r, c]) isa GridLayout && size(l) != (1, 1) unsupported_layout_error() else img = UnicodePlots.png_image(plt.o[sps += 1]; pixelsize = 32) @@ -461,16 +419,12 @@ function PlotsBase._show( nothing end -Base.show(plt::PlotsBase.Plot{UnicodePlotsBackend}) = show(stdout, plt) -Base.show(io::IO, plt::PlotsBase.Plot{UnicodePlotsBackend}) = +Base.show(plt::Plot{UnicodePlotsBackend}) = show(stdout, plt) +Base.show(io::IO, plt::Plot{UnicodePlotsBackend}) = PlotsBase._show(io, MIME("text/plain"), plt) # NOTE: _show(...) must be kept for Base.showable (src/output.jl) -function PlotsBase._show( - io::IO, - ::MIME"text/plain", - plt::PlotsBase.Plot{UnicodePlotsBackend}, -) +function PlotsBase._show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend}) PlotsBase.prepare_output(plt) nr, nc = size(plt.layout) if nr == 1 && nc == 1 # fast path @@ -490,7 +444,7 @@ function PlotsBase._show( for r in 1:nr lmax = 0 for c in 1:nc - if (l = plt.layout[r, c]) isa PlotsBase.GridLayout && size(l) != (1, 1) + if (l = plt.layout[r, c]) isa GridLayout && size(l) != (1, 1) unsupported_layout_error() else if get(l.attr, :blank, false) @@ -531,9 +485,9 @@ function PlotsBase._show( end # we only support MIME"text/plain", hence display(...) falls back to plain-text on stdout -function PlotsBase._display(plt::PlotsBase.Plot{UnicodePlotsBackend}) +function PlotsBase._display(plt::Plot{UnicodePlotsBackend}) show(stdout, plt) println(stdout) end -end # module +end # module diff --git a/PlotsBase/ext/UnitfulExt.jl b/PlotsBase/ext/UnitfulExt.jl index ede2e5713..e4e42636e 100644 --- a/PlotsBase/ext/UnitfulExt.jl +++ b/PlotsBase/ext/UnitfulExt.jl @@ -20,7 +20,8 @@ import Unitful: import PlotsBase: PlotsBase, @recipe, PlotText, Subplot, AVec, AMat, Axis import RecipesBase import LaTeXStrings: LaTeXString -import Latexify: latexify +import Latexify + using UnitfulLatexify const MissingOrQuantity = Union{Missing,<:Quantity,<:LogScaled} @@ -32,7 +33,7 @@ Main recipe @recipe function f(::Type{T}, x::T) where {T<:AbstractArray{<:MissingOrQuantity}} # COV_EXCL_LINE axisletter = plotattributes[:letter] # x, y, or z clims_types = (:contour, :contourf, :heatmap, :surface) - if axisletter === :z && get(plotattributes, :seriestype, :nothing) ∈ clims_types + if axisletter ≡ :z && get(plotattributes, :seriestype, :nothing) ∈ clims_types u = get(plotattributes, :zunit, _unit(eltype(x))) ustripattribute!(plotattributes, :clims, u) append_unit_if_needed!(plotattributes, :colorbar_title, u) @@ -59,7 +60,7 @@ function fixaxis!(attr, x, axisletter) # fix the attributes: labels, lims, ticks, marker/line stuff, etc. append_unit_if_needed!(attr, axislabel, u) ustripattribute!(attr, err, u) - if axisletter === :y + if axisletter ≡ :y ustripattribute!(attr, :ribbon, u) ustripattribute!(attr, :fillrange, u) end @@ -145,7 +146,7 @@ function fixaspectratio!(attr, u, axisletter) # Keep the default behavior (let PlotsBase figure it out) return end - if aspect_ratio === :equal + if aspect_ratio ≡ :equal aspect_ratio = 1 end #======================================================================================= @@ -158,9 +159,9 @@ function fixaspectratio!(attr, u, axisletter) made, and the default aspect ratio fixing of PlotsBase throws a `DimensionError` as it tries to compare `0 < 1u"m"`. =======================================================================================# - if axisletter === :y + if axisletter ≡ :y attr[:aspect_ratio] = aspect_ratio * u - elseif axisletter === :x + elseif axisletter ≡ :x attr[:aspect_ratio] = aspect_ratio / u end nothing @@ -231,20 +232,20 @@ append_unit_if_needed!(attr, key, u) = append_unit_if_needed!(attr, key, label::ProtectedString, u) = nothing append_unit_if_needed!(attr, key, label::UnitfulString, u) = nothing function append_unit_if_needed!(attr, key, label::Nothing, u) - attr[key] = if attr[:plot_object].backend == PlotsBase._backend_instance(:pgfplotsx) - UnitfulString(LaTeXString(latexify(u)), u) + attr[key] = if attr[:plot_object].backend == PlotsBase.backend_instance(:pgfplotsx) + UnitfulString(LaTeXString(Latexify.latexify(u)), u) else UnitfulString(string(u), u) end end function append_unit_if_needed!(attr, key, label::S, u) where {S<:AbstractString} isempty(label) && return attr[key] = UnitfulString(label, u) - if attr[:plot_object].backend == PlotsBase._backend_instance(:pgfplotsx) + if attr[:plot_object].backend == PlotsBase.backend_instance(:pgfplotsx) attr[key] = UnitfulString( LaTeXString( format_unit_label( label, - latexify(u), + Latexify.latexify(u), get(attr, Symbol(get(attr, :letter, ""), :unitformat), :round), ), ), @@ -351,8 +352,7 @@ function _unit(x) unit(x) end -function PlotsBase.pgfx_sanitize_string(s::UnitfulString) +PlotsBase.pgfx_sanitize_string(s::UnitfulString) = UnitfulString(PlotsBase.pgfx_sanitize_string(s.content), s.unit) -end end # module diff --git a/PlotsBase/src/Annotations.jl b/PlotsBase/src/Annotations.jl index 2c8c8a1c7..e73a095c9 100644 --- a/PlotsBase/src/Annotations.jl +++ b/PlotsBase/src/Annotations.jl @@ -1,14 +1,8 @@ # internal module module Annotations -using ..PlotsBase.Commons -using ..PlotsBase.Dates -using ..PlotsBase.Fonts: Font, PlotText, text, font -using ..PlotsBase.Shapes: Shape, _shapes -using ..PlotsBase.PlotMeasures: pct -using ..PlotsBase: Series, Subplot, TimeType, Length -using ..PlotsBase: is_2tuple, is3d, discrete_value! -export EachAnn, +export SeriesAnnotations, + EachAnn, series_annotations, series_annotations_shapes!, process_annotation, @@ -16,6 +10,13 @@ export EachAnn, annotations, assign_annotation_coord! +import ..PlotsBase: Series, Subplot, TimeType, is3d, discrete_value! + +using ..Commons +using ..Shapes +using ..Dates +using ..Fonts + mutable struct SeriesAnnotations strs::AVec # the labels/names font::Font @@ -69,8 +70,8 @@ function series_annotations(strs::AVec, args...) shp = arg elseif isa(arg, Font) fnt = arg - elseif isa(arg, Symbol) && haskey(_shapes, arg) - shp = _shapes[arg] + elseif isa(arg, Symbol) && haskey(Shapes._shapes, arg) + shp = Shapes._shapes[arg] elseif isa(arg, Number) scalefactor = arg, arg elseif is_2tuple(arg) @@ -87,7 +88,7 @@ end function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels) anns = series[:series_annotations] - if anns !== nothing && anns.baseshape !== nothing + if anns ≢ nothing && anns.baseshape ≢ nothing # we use baseshape to overwrite the markershape attribute # with a list of custom shapes for each msw, msh = anns.scalefactor @@ -127,7 +128,7 @@ mutable struct EachAnn end function Base.iterate(ea::EachAnn, i = 1) - (ea.anns === nothing || isempty(ea.anns.strs) || i > length(ea.y)) && return + (ea.anns ≡ nothing || isempty(ea.anns.strs) || i > length(ea.y)) && return tmp = _cycle(ea.anns.strs, i) str, fnt = if isa(tmp, PlotText) @@ -156,8 +157,7 @@ _annotationfont(sp::Subplot) = font(; _annotation(sp::Subplot, font, lab, pos...; alphabet = "abcdefghijklmnopqrstuvwxyz") = ( pos..., - lab === :auto ? text("($(alphabet[sp[:subplot_index]]))", font) : - _text_label(lab, font), + lab ≡ :auto ? text("($(alphabet[sp[:subplot_index]]))", font) : _text_label(lab, font), ) assign_annotation_coord!(axis, x) = discrete_value!(axis, x)[1] @@ -205,11 +205,11 @@ process_annotation(sp::Subplot, ann) = function _relative_position(xmin, xmax, pos::Length{:pct}, scale::Symbol) # !TODO Add more scales in the future (asinh, sqrt) ? - if scale === :log || scale === :ln + if scale ≡ :log || scale ≡ :ln exp(log(xmin) + pos.value * log(xmax / xmin)) - elseif scale === :log10 + elseif scale ≡ :log10 exp10(log10(xmin) + pos.value * log10(xmax / xmin)) - elseif scale === :log2 + elseif scale ≡ :log2 exp2(log2(xmin) + pos.value * log2(xmax / xmin)) else # :identity (linear scale) xmin + pos.value * (xmax - xmin) @@ -251,4 +251,8 @@ locate_annotation(sp::Subplot, x, y, z, label::PlotText) = (x, y, z, label) locate_annotation(sp::Subplot, pos::Symbol, label::PlotText) = locate_annotation(sp, position_multiplier[pos], label) -end # Annotations +end # module + +# ------------------------------------------------------------------- + +using .Annotations diff --git a/PlotsBase/src/Arrows.jl b/PlotsBase/src/Arrows.jl index 8d058d3c2..4bc470480 100644 --- a/PlotsBase/src/Arrows.jl +++ b/PlotsBase/src/Arrows.jl @@ -1,8 +1,9 @@ module Arrows -using ..PlotsBase.Commons export Arrow, arrow, add_arrows +using ..PlotsBase.Commons + # style is :open or :closed (for now) struct Arrow style::Symbol @@ -59,4 +60,7 @@ function add_arrows(func::Function, x::AVec, y::AVec) end end end -end # Arrows + +end # module + +using .Arrows diff --git a/PlotsBase/src/Axes.jl b/PlotsBase/src/Axes.jl index 4e604fcba..ea099aeeb 100644 --- a/PlotsBase/src/Axes.jl +++ b/PlotsBase/src/Axes.jl @@ -1,13 +1,15 @@ module Axes export Axis, Extrema, tickfont, guidefont, widen_factor, scale_inverse_scale_func -export sort_3d_axes, axes_letters, process_axis_arg!, has_ticks -import PlotsBase: get_ticks -using PlotsBase: PlotsBase, RecipesPipeline, Subplot, DefaultsDict, TimeType -using PlotsBase.Commons: _axis_defaults_byletter, _all_axis_attrs, dumpdict -using PlotsBase.Commons -using PlotsBase.Ticks -using PlotsBase.Fonts +export sort_3d_axes, axes_letters, process_axis_arg!, has_ticks, get_axis + +import ..PlotsBase +import ..PlotsBase: Subplot, DefaultsDict, TimeType, attr! + +using ..RecipesPipeline +using ..Commons +using ..Ticks +using ..Fonts const default_widen_factor = Ref(1.06) const _widen_seriestypes = ( @@ -49,7 +51,7 @@ function Axis(sp::Subplot, letter::Symbol, args...; kw...) :show => true, # show or hide the axis? (useful for linked subplots) ) - attr = DefaultsDict(explicit, _axis_defaults_byletter[letter]) + attr = DefaultsDict(explicit, Commons._axis_defaults_byletter[letter]) # update the defaults attr!(Axis([sp], attr), args...; kw...) @@ -57,9 +59,9 @@ end # properly retrieve from axis.attr, passing `:match` to the correct key Base.getindex(axis::Axis, k::Symbol) = - if (v = axis.plotattributes[k]) === :match - if haskey(Commons.Commons._match_map2, k) - axis.sps[1][Commons.Commons._match_map2[k]] + if (v = axis.plotattributes[k]) ≡ :match + if haskey(Commons._match_map2, k) + axis.sps[1][Commons._match_map2[k]] else axis[Commons._match_map[k]] end @@ -77,9 +79,9 @@ end Extrema() = Extrema(Inf, -Inf) # ------------------------------------------------------------------------- sort_3d_axes(x, y, z, letter) = - if letter === :x + if letter ≡ :x x, y, z - elseif letter === :y + elseif letter ≡ :y y, x, z else z, y, x @@ -89,13 +91,13 @@ axes_letters(sp, letter) = if RecipesPipeline.is3d(sp) sort_3d_axes(:x, :y, :z, letter) else - letter === :x ? (:x, :y) : (:y, :x) + letter ≡ :x ? (:x, :y) : (:y, :x) end scale_inverse_scale_func(scale::Symbol) = ( RecipesPipeline.scale_func(scale), RecipesPipeline.inverse_scale_func(scale), - scale === :identity, + scale ≡ :identity, ) function get_axis(sp::Subplot, letter::Symbol) axissym = get_attr_symbol(letter, :axis) @@ -116,22 +118,22 @@ function Commons.axis_limits( ex = axis[:extrema] amin, amax = ex.emin, ex.emax lims = process_limits(axis[:lims], axis) - lims === nothing && warn_invalid_limits(axis[:lims], letter) + lims ≡ nothing && warn_invalid_limits(axis[:lims], letter) if (has_user_lims = lims isa Tuple) lmin, lmax = lims if lmin isa Number && isfinite(lmin) amin = lmin elseif lmin isa Symbol - lmin === :auto || @warn "Invalid min $(letter)limit" lmin + lmin ≡ :auto || @warn "Invalid min $(letter)limit" lmin end if lmax isa Number && isfinite(lmax) amax = lmax elseif lmax isa Symbol - lmax === :auto || @warn "Invalid max $(letter)limit" lmax + lmax ≡ :auto || @warn "Invalid max $(letter)limit" lmax end end - if lims === :symmetric + if lims ≡ :symmetric amax = max(abs(amin), abs(amax)) amin = -amax end @@ -142,15 +144,15 @@ function Commons.axis_limits( amin, amax = zero(amin), one(amax) end if ispolar(axis.sps[1]) - if axis[:letter] === :x + if axis[:letter] ≡ :x amin, amax = 0, 2π - elseif lims === :auto + elseif lims ≡ :auto # widen max radius so ticks dont overlap with theta axis amin, amax = 0, amax + 0.1abs(amax - amin) end - elseif lims_factor !== nothing + elseif lims_factor ≢ nothing amin, amax = scale_lims(amin, amax, lims_factor, axis[:scale]) - elseif lims === :round + elseif lims ≡ :round amin, amax = round_limits(amin, amax, axis[:scale]) end @@ -159,14 +161,14 @@ function Commons.axis_limits( !has_user_lims && consider_aspect && letter in (:x, :y) && - !(aspect_ratio === :none || RecipesPipeline.is3d(:sp)) + !(aspect_ratio ≡ :none || RecipesPipeline.is3d(:sp)) ) aspect_ratio = aspect_ratio isa Number ? aspect_ratio : 1 area = PlotsBase.plotarea(sp) plot_ratio = PlotsBase.height(area) / PlotsBase.width(area) dist = amax - amin - factor = if letter === :x + factor = if letter ≡ :x ydist, = axis_limits(sp, :y, widen_factor(sp[:yaxis]), false) |> collect |> diff axis_ratio = aspect_ratio * ydist / dist axis_ratio / plot_ratio @@ -195,12 +197,12 @@ function widen_factor(axis::Axis; factor = default_widen_factor[]) elseif widen isa Number return widen else - widen === :auto || @warn "Invalid value specified for `widen`: $widen" + widen ≡ :auto || @warn "Invalid value specified for `widen`: $widen" end # automatic behavior: widen if limits aren't specified and series type is appropriate lims = process_limits(axis[:lims], axis) - (lims isa Tuple || lims === :round) && return + (lims isa Tuple || lims ≡ :round) && return for sp in axis.sps, series in series_list(sp) series.plotattributes[:seriestype] in _widen_seriestypes && return factor end @@ -249,13 +251,15 @@ Scale the limits of the axis specified by `letter` (one of `:x`, `:y`, `:z`) by given `factor` around the limits' middle point. If `letter` is omitted, all axes are affected. """ -function scale_lims!(sp::Subplot, letter, factor) +function Commons.scale_lims!(sp::Subplot, letter, factor) axis = get_axis(sp, letter) from, to = PlotsBase.get_sp_lims(sp, letter) axis[:lims] = scale_lims(from, to, factor, axis[:scale]) end -scale_lims!(factor::Number) = scale_lims!(PlotsBase.current(), factor) -scale_lims!(letter::Symbol, factor) = scale_lims!(PlotsBase.current(), letter, factor) +Commons.scale_lims!(factor::Number) = scale_lims!(PlotsBase.current(), factor) +Commons.scale_lims!(letter::Symbol, factor) = + scale_lims!(PlotsBase.current(), letter, factor) + #---------------------------------------------------------------------- function process_axis_arg!(plotattributes::AKW, arg, letter = "") T = typeof(arg) @@ -282,7 +286,7 @@ function process_axis_arg!(plotattributes::AKW, arg, letter = "") elseif T <: AVec plotattributes[get_attr_symbol(letter, :ticks)] = arg - elseif arg === nothing + elseif arg ≡ nothing plotattributes[get_attr_symbol(letter, :ticks)] = [] elseif T <: Bool || arg in Commons._all_showaxis_attrs @@ -303,10 +307,10 @@ function process_axis_arg!(plotattributes::AKW, arg, letter = "") end end -has_ticks(axis::Axis) = get(axis, :ticks, nothing) |> PlotsBase.Ticks._has_ticks +has_ticks(axis::Axis) = _has_ticks(get(axis, :ticks, nothing)) # update an Axis object with magic args and keywords -function attr!(axis::Axis, args...; kw...) +function PlotsBase.attr!(axis::Axis, args...; kw...) # first process args plotattributes = axis.plotattributes foreach(arg -> process_axis_arg!(plotattributes, arg), args) @@ -317,9 +321,9 @@ function attr!(axis::Axis, args...; kw...) # then override for any keywords... only those keywords that already exists in plotattributes for (k, v) in kw haskey(plotattributes, k) || continue - if k === :discrete_values + if k ≡ :discrete_values foreach(x -> discrete_value!(axis, x), v) # add these discrete values to the axis - elseif k === :lims && isa(v, NTuple{2,TimeType}) + elseif k ≡ :lims && isa(v, NTuple{2,TimeType}) plotattributes[k] = (v[1].instant.periods.value, v[2].instant.periods.value) else plotattributes[k] = v @@ -336,7 +340,7 @@ end # ------------------------------------------------------------------------- -Base.show(io::IO, axis::Axis) = dumpdict(io, axis.plotattributes, "Axis") +Base.show(io::IO, axis::Axis) = Commons.dumpdict(io, axis.plotattributes, "Axis") ignorenan_extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax)) tickfont(ax::Axis) = font(; @@ -365,7 +369,7 @@ function _update_axis( ) # build the KW of arguments from the letter version (i.e. xticks --> ticks) kw = KW() - for k in _all_axis_attrs + for k in Commons._all_axis_attrs # first get the args without the letter: `tickfont = font(10)` # note: we don't pop because we want this to apply to all axes! (delete after all have finished) if haskey(plotattributes_in, k) @@ -399,15 +403,20 @@ end """ returns (continuous_values, discrete_values) for the ticks on this axis """ -function get_ticks(sp::Subplot, axis::Axis; update = true, formatter = axis[:formatter]) +function Commons.get_ticks( + sp::Subplot, + axis::Axis; + update = true, + formatter = axis[:formatter], +) if update || !haskey(axis.plotattributes, :optimized_ticks) dvals = axis[:discrete_values] ticks = _transform_ticks(axis[:ticks], axis) axis.plotattributes[:optimized_ticks] = if ( - axis[:letter] === :x && + axis[:letter] ≡ :x && ticks isa Symbol && - ticks !== :none && + ticks ≢ :none && !isempty(dvals) && ispolar(sp) ) @@ -415,7 +424,7 @@ function get_ticks(sp::Subplot, axis::Axis; update = true, formatter = axis[:for else cvals = axis[:continuous_values] alims = axis_limits(sp, axis[:letter]) - get_ticks(ticks, cvals, dvals, alims, axis[:scale], formatter) + Commons.get_ticks(ticks, cvals, dvals, alims, axis[:scale], formatter) end end axis.plotattributes[:optimized_ticks] @@ -457,6 +466,8 @@ function PlotsBase.expand_extrema!(axis::Axis, v::AVec{N}) where {N<:Number} ex end +end # Axes + # ------------------------------------------------------------------------- -end # Axes +using .Axes diff --git a/PlotsBase/src/BezierCurves.jl b/PlotsBase/src/BezierCurves.jl index cf9eb5119..1b7cc051e 100644 --- a/PlotsBase/src/BezierCurves.jl +++ b/PlotsBase/src/BezierCurves.jl @@ -19,4 +19,8 @@ end PlotsBase.coords(curve::BezierCurve, n::Integer = 30; range = [0, 1]) = map(curve, Base.range(first(range), stop = last(range), length = n)) -end +end # module + +# ------------------------------------------------------------------- + +using .BezierCurves diff --git a/PlotsBase/src/Colorbars.jl b/PlotsBase/src/Colorbars.jl index cfac48642..17a55a800 100644 --- a/PlotsBase/src/Colorbars.jl +++ b/PlotsBase/src/Colorbars.jl @@ -1,16 +1,18 @@ module Colorbars -export colorbar_style, - get_clims, update_clims, hascolorbar, get_colorbar_ticks, _update_subplot_colorbars -using PlotsBase.Commons: Commons, NaNMath, ignorenan_extrema -using PlotsBase.PlotsSeries -using PlotsBase.Subplots: Subplot, series_list -using PlotsBase.Surfaces: AbstractSurface -using PlotsBase.Ticks -using PlotsBase.Ticks: _transform_ticks -import PlotsBase.Commons.get_clims - -# These functions return an operator for use in `get_clims(::Seres, op)` +export colorbar_style, get_clims, update_clims, hascolorbar +export get_colorbar_ticks, _update_subplot_colorbars + +import ..Commons: NaNMath, ignorenan_extrema, get_clims + +using ..Subplots: Subplot, series_list +using ..Surfaces: AbstractSurface +using ..Ticks: _transform_ticks +using ..DataSeries +using ..Commons +using ..Ticks + +# these functions return an operator for use in `get_clims(::Seres, op)` process_clims(lims::Tuple{<:Number,<:Number}) = (zlims -> ifelse.(isfinite.(lims), lims, zlims)) ∘ ignorenan_extrema process_clims(s::Union{Symbol,Nothing,Missing}) = ignorenan_extrema @@ -31,13 +33,10 @@ function update_clims(sp::Subplot, op = process_clims(sp[:clims]))::Tuple{Float6 for series in series_list(sp) if series[:colorbar_entry]::Bool # Avoid calling the inner `update_clims` if at all possible; dynamic dispatch hell - if ( - series[:seriestype] ∈ Commons._z_colored_series && - series[:z] !== nothing - ) || - series[:line_z] !== nothing || - series[:marker_z] !== nothing || - series[:fill_z] !== nothing + if (series[:seriestype] ∈ Commons._z_colored_series && series[:z] ≢ nothing) || + series[:line_z] ≢ nothing || + series[:marker_z] ≢ nothing || + series[:fill_z] ≢ nothing zmin, zmax = _update_clims(zmin, zmax, update_clims(series, op)...) else zmin, zmax = _update_clims(zmin, zmax, NaN, NaN) @@ -77,16 +76,16 @@ function update_clims(series::Series, op = ignorenan_extrema)::Tuple{Float64,Flo zmin, zmax = Inf, -Inf # keeping this unrolled has higher performance - if series[:seriestype] ∈ Commons._z_colored_series && series[:z] !== nothing + if series[:seriestype] ∈ Commons._z_colored_series && series[:z] ≢ nothing zmin, zmax = update_clims(zmin, zmax, series[:z], op) end - if series[:line_z] !== nothing + if series[:line_z] ≢ nothing zmin, zmax = update_clims(zmin, zmax, series[:line_z], op) end - if series[:marker_z] !== nothing + if series[:marker_z] ≢ nothing zmin, zmax = update_clims(zmin, zmax, series[:marker_z], op) end - if series[:fill_z] !== nothing + if series[:fill_z] ≢ nothing zmin, zmax = update_clims(zmin, zmax, series[:fill_z], op) end return series[:clims_calculated] = zmin <= zmax ? (zmin, zmax) : (NaN, NaN) @@ -116,16 +115,16 @@ function colorbar_style(series::Series) elseif iscontour(series) cbar_lines elseif series[:seriestype] ∈ (:heatmap, :surface) || - any(series[z] !== nothing for z in (:marker_z, :line_z, :fill_z)) + any(series[z] ≢ nothing for z in (:marker_z, :line_z, :fill_z)) cbar_gradient else nothing end end -hascolorbar(series::Series) = colorbar_style(series) !== nothing +hascolorbar(series::Series) = colorbar_style(series) ≢ nothing hascolorbar(sp::Subplot) = - sp[:colorbar] !== :none && any(hascolorbar(s) for s in series_list(sp)) + sp[:colorbar] ≢ :none && any(hascolorbar(s) for s in series_list(sp)) function get_colorbar_ticks(sp::Subplot; update = true, formatter = sp[:colorbar_formatter]) if update || !haskey(sp.attr, :colorbar_optimized_ticks) @@ -140,7 +139,12 @@ function get_colorbar_ticks(sp::Subplot; update = true, formatter = sp[:colorbar return sp.attr[:colorbar_optimized_ticks] end -# Dynamic callback from the pipeline if needed +# dynamic callback from the pipeline if needed _update_subplot_colorbars(sp::Subplot) = update_clims(sp) _update_subplot_colorbars(sp::Subplot, series::Series) = update_clims(sp, series) -end # Colorbars + +end # module + +# ------------------------------------------------------------------- + +using .Colorbars diff --git a/PlotsBase/src/Commons/Commons.jl b/PlotsBase/src/Commons/Commons.jl index 5d995153d..a58e53b30 100644 --- a/PlotsBase/src/Commons/Commons.jl +++ b/PlotsBase/src/Commons/Commons.jl @@ -1,10 +1,8 @@ "Things that should be common to all backends and frontend modules" module Commons -export AVec, AMat, KW, AKW, TicksArgs -export PlotsBase, PLOTS_SEED -export _haligns, _valigns, _cbar_width -# Functions +export AVec, + AMat, KW, AKW, TicksArgs, PlotsBase, PLOTS_SEED, _haligns, _valigns, _cbar_width export get_subplot, coords, ispolar, @@ -38,18 +36,35 @@ export anynan, ignorenan_maximum, ignorenan_mean, ignorenan_minimum -#exports from args.jl +export istuple, isvector, ismatrix, isscalar, is_2tuple export default, wraptuple, merge_with_base_supported -using PlotsBase: PlotsBase, Printf, NaNMath, cgrad -import PlotsBase: RecipesPipeline -using PlotsBase.Colors: Colorant, @colorant_str -using PlotsBase.ColorTypes: alpha -using PlotsBase.Measures: mm, BoundingBox -using PlotsBase.PlotUtils: PlotUtils, ColorPalette, plot_color, isdark, ColorGradient -using PlotsBase.RecipesBase -using PlotsBase: DEFAULT_LINEWIDTH -using PlotsBase: Statistics +export px, pct, plotarea, plotarea! +export width, height, leftpad, toppad, bottompad, rightpad +export origin, left, right, bottom, top, bbox, bbox! +export DEFAULT_BBOX, DEFAULT_MINPAD, DEFAULT_LINEWIDTH +export MM_PER_PX, MM_PER_INCH, DPI, PX_PER_INCH + +export GridLayout, EmptyLayout, RootLayout +export BBox, BoundingBox, mm, cm, inch, pt, w, h +export bbox_to_pcts, xy_mm_to_pcts +export Length, AbsoluteLength, Measure +export to_pixels, ispositive, get_ticks, scale_lims! + +import Measures: + Measures, Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, w, h +import PlotUtils: PlotUtils, ColorPalette, plot_color, isdark, ColorGradient +import PlotsBase: PlotsBase, RecipesPipeline, cgrad + +using ..Colors: Colorant, @colorant_str +using ..ColorTypes: alpha +using ..RecipesBase +using ..Statistics +using ..NaNMath +using ..Printf + +const width = Measures.width +const height = Measures.height const AVec = AbstractVector const AMat = AbstractMatrix @@ -57,14 +72,9 @@ const KW = Dict{Symbol,Any} const AKW = AbstractDict{Symbol,Any} const TicksArgs = Union{AVec{T},Tuple{AVec{T},AVec{S}},Symbol} where {T<:Real,S<:AbstractString} -const PLOTS_SEED = 1234 -const PX_PER_INCH = 100 -const DPI = PX_PER_INCH -const MM_PER_INCH = 25.4 -const MM_PER_PX = MM_PER_INCH / PX_PER_INCH + const _haligns = :hcenter, :left, :right const _valigns = :vcenter, :top, :bottom -const _cbar_width = 5mm const _all_scales = [:identity, :ln, :log2, :log10, :asinh, :sqrt] const _log_scales = [:ln, :log2, :log10] const _log_scale_bases = Dict(:ln => ℯ, :log2 => 2.0, :log10 => 10.0) @@ -90,14 +100,26 @@ const _segmenting_vector_attributes = ( const _segmenting_array_attributes = :line_z, :fill_z, :marker_z const _debug = Ref(false) -function get_subplot end -function get_clims end -function series_list end -function coords end -function ispolar end -function expand_extrema! end -function axis_limits end -function preprocess_attributes! end +# docs.julialang.org/en/v1/manual/methods/#Empty-generic-functions +macro generic_functions(args...) + blk = Expr(:block) + foreach(arg -> push!(blk.args, :(function $arg end)), args) + blk |> esc +end + +@generic_functions get_ticks get_subplot get_clims +@generic_functions series_list coords ispolar axis_limits +@generic_functions expand_extrema! preprocess_attributes! scale_lims! + +@generic_functions width height leftpad toppad bottompad rightpad +@generic_functions origin left right bottom top +@generic_functions plotarea plotarea! + +include("measures.jl") + +using ..RecipesBase: AbstractLayout +include("layouts.jl") + # --------------------------------------------------------------- wraptuple(x::Tuple) = x wraptuple(x) = (x,) @@ -109,12 +131,12 @@ all_lineLtypes(arg) = true_or_all_true(a -> get(Commons._typeAliases, a, a) in Commons._all_seriestypes, arg) all_styles(arg) = true_or_all_true(a -> get(Commons._styleAliases, a, a) in Commons._all_styles, arg) -all_shapes(arg) = (true_or_all_true( +all_shapes(arg) = true_or_all_true( a -> get(Commons._marker_aliases, a, a) in Commons._all_markers || a isa PlotsBase.Shape, arg, -)) +) all_alphas(arg) = true_or_all_true( a -> (typeof(a) <: Real && a > 0 && a < 1) || ( @@ -131,17 +153,30 @@ include("attrs.jl") function _override_seriestype_check(plotattributes::AKW, st::Symbol) # do we want to override the series type? if !RecipesPipeline.is3d(st) && st ∉ (:contour, :contour3d, :quiver) - if (z = plotattributes[:z]) !== nothing && + if (z = plotattributes[:z]) ≢ nothing && size(plotattributes[:x]) == size(plotattributes[:y]) == size(z) - st = st === :scatter ? :scatter3d : :path3d + st = st ≡ :scatter ? :scatter3d : :path3d plotattributes[:seriestype] = st end end st end -"These should only be needed in frontend modules" -PlotsBase.@ScopeModule( +macro ScopeModule(mod::Symbol, parent::Symbol, symbols...) + import_ex = Expr( + :import, + Expr( + :(:), + Expr(:., :., :., parent), + (Expr(:., s isa Expr ? s.args[1] : s) for s in symbols)..., + ), + ) + export_ex = Expr(:export, (s isa Expr ? s.args[1] : s for s in symbols)...) + Expr(:module, true, mod, Expr(:block, import_ex, export_ex)) |> esc +end + +"these should only be needed in frontend modules" +@ScopeModule( Frontend, Commons, _subplot_defaults, @@ -157,7 +192,7 @@ PlotsBase.@ScopeModule( function fg_color(plotattributes::AKW) fg = get(plotattributes, :foreground_color, :auto) - if fg === :auto + if fg ≡ :auto bg = plot_color(get(plotattributes, :background_color, :white)) fg = alpha(bg) > 0 && isdark(bg) ? colorant"white" : colorant"black" else @@ -165,15 +200,36 @@ function fg_color(plotattributes::AKW) end end function color_or_nothing!(plotattributes, k::Symbol) - plotattributes[k] = (v = plotattributes[k]) === :match ? v : plot_color(v) + plotattributes[k] = (v = plotattributes[k]) ≡ :match ? v : plot_color(v) nothing end +istuple(::Tuple) = true +istuple(::Any) = false +isvector(::AVec) = true +isvector(::Any) = false +ismatrix(::AMat) = true +ismatrix(::Any) = false +isscalar(::Real) = true +isscalar(::Any) = false + +is_2tuple(v) = typeof(v) <: Tuple && length(v) == 2 + # cache joined symbols so they can be looked up instead of constructed each time const _attrsymbolcache = Dict{Symbol,Dict{Symbol,Symbol}}() -get_attr_symbol(letter::Symbol, keyword::String) = get_attr_symbol(letter, Symbol(keyword)) get_attr_symbol(letter::Symbol, keyword::Symbol) = _attrsymbolcache[letter][keyword] +get_attr_symbol(letter::Symbol, keyword::String) = get_attr_symbol(letter, Symbol(keyword)) + +new_attr_dict!(letter::Symbol)::Dict{Symbol,Symbol} = + get!(() -> Dict{Symbol,Symbol}(), _attrsymbolcache, letter) + +# NOTE: using `keyword::String` allows to disambiguate argument order +set_attr_symbol!(letter::Symbol, keyword::String) = + let letter_keyword = Symbol(letter, keyword) + _attrsymbolcache[letter][Symbol(keyword)] = 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))] @@ -260,10 +316,10 @@ reverse_if(x, cond) = cond ? reverse(x) : x function get_aspect_ratio(sp) ar = sp[:aspect_ratio] check_aspect_ratio(ar) - if ar === :auto + if ar ≡ :auto ar = :none for series in series_list(sp) - if series[:seriestype] === :image + if series[:seriestype] ≡ :image ar = :equal end end diff --git a/PlotsBase/src/Commons/aliases.jl b/PlotsBase/src/Commons/aliases.jl index 1a8ed86a7..f7a887e75 100644 --- a/PlotsBase/src/Commons/aliases.jl +++ b/PlotsBase/src/Commons/aliases.jl @@ -9,7 +9,7 @@ function aliases_and_autopick( options::AVec, plotIndex::Int, ) - if plotattributes[sym] === :auto + if plotattributes[sym] ≡ :auto plotattributes[sym] = autopick_ignore_none_auto(options, plotIndex) elseif haskey(aliases, plotattributes[sym]) plotattributes[sym] = aliases[plotattributes[sym]] diff --git a/PlotsBase/src/Commons/attrs.jl b/PlotsBase/src/Commons/attrs.jl index 5f453a477..6e9ad60c4 100644 --- a/PlotsBase/src/Commons/attrs.jl +++ b/PlotsBase/src/Commons/attrs.jl @@ -5,7 +5,7 @@ const _keyAliases = Dict{Symbol,Symbol}() function add_aliases(sym::Symbol, aliases::Symbol...) for alias in aliases - (haskey(_keyAliases, alias) || alias === sym) && return + (haskey(_keyAliases, alias) || alias ≡ sym) && return _keyAliases[alias] = sym end nothing @@ -695,11 +695,11 @@ function default(k::Symbol) haskey(defaults, k) && return defaults[k] end haskey(_axis_defaults, k) && return _axis_defaults[k] - if (axis_k = parse_axis_kw(k)) !== nothing + if (axis_k = parse_axis_kw(k)) ≢ nothing letter, key = axis_k return _axis_defaults_byletter[letter][key] end - k === :letter && return k # for type recipe processing + k ≡ :letter && return k # for type recipe processing missing end @@ -715,7 +715,7 @@ function default(k::Symbol, v) _axis_defaults[k] = v return v end - if (axis_k = parse_axis_kw(k)) !== nothing + if (axis_k = parse_axis_kw(k)) ≢ nothing letter, key = axis_k _axis_defaults_byletter[letter][key] = v return v @@ -746,7 +746,7 @@ end # if arg is a valid color value, then set plotattributes[csym] and return true function handle_colors!(plotattributes::AKW, arg, csym::Symbol) try - plotattributes[csym] = if arg === :auto + plotattributes[csym] = if arg ≡ :auto :auto else plot_color(arg) @@ -767,22 +767,18 @@ function process_line_attr(plotattributes::AKW, arg) plotattributes[:linestyle] = arg elseif typeof(arg) <: PlotsBase.Stroke - arg.width === nothing || (plotattributes[:linewidth] = arg.width) - arg.color === nothing || ( - plotattributes[:linecolor] = - arg.color === :auto ? :auto : plot_color(arg.color) - ) - arg.alpha === nothing || (plotattributes[:linealpha] = arg.alpha) - arg.style === nothing || (plotattributes[:linestyle] = arg.style) + arg.width ≡ nothing || (plotattributes[:linewidth] = arg.width) + arg.color ≡ nothing || + (plotattributes[:linecolor] = arg.color ≡ :auto ? :auto : plot_color(arg.color)) + arg.alpha ≡ nothing || (plotattributes[:linealpha] = arg.alpha) + arg.style ≡ nothing || (plotattributes[:linestyle] = arg.style) elseif typeof(arg) <: PlotsBase.Brush - arg.size === nothing || (plotattributes[:fillrange] = arg.size) - arg.color === nothing || ( - plotattributes[:fillcolor] = - arg.color === :auto ? :auto : plot_color(arg.color) - ) - arg.alpha === nothing || (plotattributes[:fillalpha] = arg.alpha) - arg.style === nothing || (plotattributes[:fillstyle] = arg.style) + arg.size ≡ nothing || (plotattributes[:fillrange] = arg.size) + arg.color ≡ nothing || + (plotattributes[:fillcolor] = arg.color ≡ :auto ? :auto : plot_color(arg.color)) + arg.alpha ≡ nothing || (plotattributes[:fillalpha] = arg.alpha) + arg.style ≡ nothing || (plotattributes[:fillstyle] = arg.style) elseif typeof(arg) <: PlotsBase.Arrow || arg in (:arrow, :arrows) plotattributes[:arrow] = arg @@ -811,21 +807,21 @@ function process_marker_attr(plotattributes::AKW, arg) plotattributes[:markerstrokestyle] = arg elseif typeof(arg) <: PlotsBase.Stroke - arg.width === nothing || (plotattributes[:markerstrokewidth] = arg.width) - arg.color === nothing || ( + arg.width ≡ nothing || (plotattributes[:markerstrokewidth] = arg.width) + arg.color ≡ nothing || ( plotattributes[:markerstrokecolor] = - arg.color === :auto ? :auto : plot_color(arg.color) + arg.color ≡ :auto ? :auto : plot_color(arg.color) ) - arg.alpha === nothing || (plotattributes[:markerstrokealpha] = arg.alpha) - arg.style === nothing || (plotattributes[:markerstrokestyle] = arg.style) + arg.alpha ≡ nothing || (plotattributes[:markerstrokealpha] = arg.alpha) + arg.style ≡ nothing || (plotattributes[:markerstrokestyle] = arg.style) elseif typeof(arg) <: PlotsBase.Brush - arg.size === nothing || (plotattributes[:markersize] = arg.size) - arg.color === nothing || ( + arg.size ≡ nothing || (plotattributes[:markersize] = arg.size) + arg.color ≡ nothing || ( plotattributes[:markercolor] = - arg.color === :auto ? :auto : plot_color(arg.color) + arg.color ≡ :auto ? :auto : plot_color(arg.color) ) - arg.alpha === nothing || (plotattributes[:markeralpha] = arg.alpha) + arg.alpha ≡ nothing || (plotattributes[:markeralpha] = arg.alpha) # linealpha elseif all_alphas(arg) @@ -848,13 +844,11 @@ end function process_fill_attr(plotattributes::AKW, arg) # fr = get(plotattributes, :fillrange, 0) if typeof(arg) <: PlotsBase.Brush - arg.size === nothing || (plotattributes[:fillrange] = arg.size) - arg.color === nothing || ( - plotattributes[:fillcolor] = - arg.color === :auto ? :auto : plot_color(arg.color) - ) - arg.alpha === nothing || (plotattributes[:fillalpha] = arg.alpha) - arg.style === nothing || (plotattributes[:fillstyle] = arg.style) + arg.size ≡ nothing || (plotattributes[:fillrange] = arg.size) + arg.color ≡ nothing || + (plotattributes[:fillcolor] = arg.color ≡ :auto ? :auto : plot_color(arg.color)) + arg.alpha ≡ nothing || (plotattributes[:fillalpha] = arg.alpha) + arg.style ≡ nothing || (plotattributes[:fillstyle] = arg.style) elseif typeof(arg) <: Bool plotattributes[:fillrange] = arg ? 0 : nothing @@ -886,15 +880,15 @@ function process_grid_attr!(plotattributes::AKW, arg, letter) plotattributes[get_attr_symbol(letter, :gridstyle)] = arg elseif typeof(arg) <: PlotsBase.Stroke - arg.width === nothing || + arg.width ≡ nothing || (plotattributes[get_attr_symbol(letter, :gridlinewidth)] = arg.width) - arg.color === nothing || ( + arg.color ≡ nothing || ( plotattributes[get_attr_symbol(letter, :foreground_color_grid)] = arg.color in (:auto, :match) ? :match : plot_color(arg.color) ) - arg.alpha === nothing || + arg.alpha ≡ nothing || (plotattributes[get_attr_symbol(letter, :gridalpha)] = arg.alpha) - arg.style === nothing || + arg.style ≡ nothing || (plotattributes[get_attr_symbol(letter, :gridstyle)] = arg.style) # linealpha @@ -924,15 +918,15 @@ function process_minor_grid_attr!(plotattributes::AKW, arg, letter) plotattributes[get_attr_symbol(letter, :minorgrid)] = true elseif typeof(arg) <: PlotsBase.Stroke - arg.width === nothing || + arg.width ≡ nothing || (plotattributes[get_attr_symbol(letter, :minorgridlinewidth)] = arg.width) - arg.color === nothing || ( + arg.color ≡ nothing || ( plotattributes[get_attr_symbol(letter, :foreground_color_minor_grid)] = arg.color in (:auto, :match) ? :match : plot_color(arg.color) ) - arg.alpha === nothing || + arg.alpha ≡ nothing || (plotattributes[get_attr_symbol(letter, :minorgridalpha)] = arg.alpha) - arg.style === nothing || + arg.style ≡ nothing || (plotattributes[get_attr_symbol(letter, :minorgridstyle)] = arg.style) plotattributes[get_attr_symbol(letter, :minorgrid)] = true @@ -977,7 +971,7 @@ end Symbol(fontname, :valign) --> arg.valign Symbol(fontname, :rotation) --> arg.rotation Symbol(fontname, :color) --> arg.color - elseif arg === :center + elseif arg ≡ :center Symbol(fontname, :halign) --> :hcenter Symbol(fontname, :valign) --> :vcenter elseif arg ∈ _haligns @@ -1013,7 +1007,7 @@ function _add_markershape(plotattributes::AKW) # add the markershape if it needs to be added... hack to allow "m=10" to add a shape, # and still allow overriding in _apply_recipe ms = pop!(plotattributes, :markershape_to_add, :none) - if !haskey(plotattributes, :markershape) && ms !== :none + if !haskey(plotattributes, :markershape) && ms ≢ :none plotattributes[:markershape] = ms end end @@ -1046,7 +1040,7 @@ function convert_legend_value(val::Symbol) :inline, ) val - elseif val === :horizontal + elseif val ≡ :horizontal -1 else error("Invalid symbol for legend: $val") @@ -1150,7 +1144,7 @@ ensure_gradient!(plotattributes::AKW, csym::Symbol, asym::Symbol) = # get a good default linewidth... 0 for surface and heatmaps _replace_linewidth(plotattributes::AKW) = - if plotattributes[:linewidth] === :auto + if plotattributes[:linewidth] ≡ :auto plotattributes[:linewidth] = (get(plotattributes, :seriestype, :path) ∉ (:surface, :heatmap, :image)) * DEFAULT_LINEWIDTH[] @@ -1161,9 +1155,9 @@ label_to_string(label::Bool, series_plotindex) = label_to_string(label::Nothing, series_plotindex) = "" label_to_string(label::Missing, series_plotindex) = "" label_to_string(label::Symbol, series_plotindex) = - if label === :auto + if label ≡ :auto string("y", series_plotindex) - elseif label === :none + elseif label ≡ :none "" else throw(ArgumentError("unsupported symbol $(label) passed to `label`")) @@ -1192,8 +1186,8 @@ Also creates pluralized and non-underscore aliases for these keywords. """ macro add_attributes(level, expr, match_table) expr = macroexpand(__module__, expr) # to expand @static - expr isa Expr && expr.head === :struct || error("Invalid usage of @add_attributes") - if (T = expr.args[2]) isa Expr && T.head === :<: + expr isa Expr && expr.head ≡ :struct || error("Invalid usage of @add_attributes") + if (T = expr.args[2]) isa Expr && T.head ≡ :<: T = T.args[1] end @@ -1239,12 +1233,12 @@ function _splitdef!(blk, key_dict) # var continue elseif ei isa Expr - if ei.head === :(=) + if ei.head ≡ :(=) lhs = ei.args[1] if lhs isa Symbol # var = defexpr var = lhs - elseif lhs isa Expr && lhs.head === :(::) && lhs.args[1] isa Symbol + elseif lhs isa Expr && lhs.head ≡ :(::) && lhs.args[1] isa Symbol # var::T = defexpr var = lhs.args[1] type = lhs.args[2] @@ -1262,11 +1256,11 @@ function _splitdef!(blk, key_dict) defexpr = ei.args[2] # defexpr key_dict[var] = defexpr blk.args[i] = lhs - elseif ei.head === :(::) && ei.args[1] isa Symbol + elseif ei.head ≡ :(::) && ei.args[1] isa Symbol # var::Typ var = ei.args[1] key_dict[var] = defexpr - elseif ei.head === :block + elseif ei.head ≡ :block # can arise with use of @static inside type decl _kwdef!(ei, value_attrs, key_attrs) end diff --git a/PlotsBase/src/Commons/layouts.jl b/PlotsBase/src/Commons/layouts.jl new file mode 100644 index 000000000..52cc11201 --- /dev/null +++ b/PlotsBase/src/Commons/layouts.jl @@ -0,0 +1,167 @@ +make_measure_hor(n::Number) = n * w +make_measure_hor(m::Measure) = m + +make_measure_vert(n::Number) = n * h +make_measure_vert(m::Measure) = m + +""" + bbox(x, y, w, h [,originargs...]) + bbox(layout) + +Create a bounding box for plotting +""" +function bbox(x, y, w, h, oarg1::Symbol, originargs::Symbol...) + oargs = vcat(oarg1, originargs...) + orighor = :left + origver = :top + for oarg in oargs + if oarg ≡ :center + orighor = origver = oarg + elseif oarg in (:left, :right, :hcenter) + orighor = oarg + elseif oarg in (:top, :bottom, :vcenter) + origver = oarg + else + @warn "Unused origin arg in bbox construction: $oarg" + end + end + bbox(x, y, w, h; h_anchor = orighor, v_anchor = origver) +end + +# create a new bbox +function bbox(x, y, width, height; h_anchor = :left, v_anchor = :top) + x = make_measure_hor(x) + y = make_measure_vert(y) + width = make_measure_hor(width) + height = make_measure_vert(height) + left = if h_anchor ≡ :left + x + elseif h_anchor in (:center, :hcenter) + 0.5w - 0.5width + x + else + 1w - x - width + end + top = if v_anchor ≡ :top + y + elseif v_anchor in (:center, :vcenter) + 0.5h - 0.5height + y + else + 1h - y - height + end + BoundingBox(left, top, width, height) +end + +# NOTE: (0,0) is the top-left !!! +left(bbox::BoundingBox) = bbox.x0[1] +top(bbox::BoundingBox) = bbox.x0[2] +right(bbox::BoundingBox) = left(bbox) + width(bbox) +bottom(bbox::BoundingBox) = top(bbox) + height(bbox) +origin(bbox::BoundingBox) = left(bbox) + width(bbox) / 2, top(bbox) + height(bbox) / 2 +Base.size(bbox::BoundingBox) = (width(bbox), height(bbox)) + +# ----------------------------------------------------------- +# AbstractLayout + +left(layout::AbstractLayout) = left(bbox(layout)) +top(layout::AbstractLayout) = top(bbox(layout)) +right(layout::AbstractLayout) = right(bbox(layout)) +bottom(layout::AbstractLayout) = bottom(bbox(layout)) +width(layout::AbstractLayout) = width(bbox(layout)) +height(layout::AbstractLayout) = height(bbox(layout)) + +leftpad(layout::AbstractLayout) = 0mm +toppad(layout::AbstractLayout) = 0mm +rightpad(layout::AbstractLayout) = 0mm +bottompad(layout::AbstractLayout) = 0mm + +leftpad(pad) = pad[1] +toppad(pad) = pad[2] +rightpad(pad) = pad[3] +bottompad(pad) = pad[4] + +Base.show(io::IO, layout::AbstractLayout) = print(io, "$(typeof(layout))$(size(layout))") + +# this is the available area for drawing everything in this layout... as percentages of total canvas +bbox(layout::AbstractLayout) = layout.bbox +bbox!(layout::AbstractLayout, bb::BoundingBox) = (layout.bbox = bb) + +# layouts are recursive, tree-like structures, and most will have a parent field +Base.parent(layout::AbstractLayout) = layout.parent +parent_bbox(layout::AbstractLayout) = bbox(parent(layout)) + +# ----------------------------------------------------------- +# RootLayout + +# this is the parent of the top-level layout +struct RootLayout <: AbstractLayout end + +Base.show(io::IO, layout::RootLayout) = Base.show_default(io, layout) +Base.parent(::RootLayout) = nothing +parent_bbox(::RootLayout) = DEFAULT_BBOX[] +bbox(::RootLayout) = DEFAULT_BBOX[] + +# ----------------------------------------------------------- +# EmptyLayout + +# contains blank space +mutable struct EmptyLayout <: AbstractLayout + parent::AbstractLayout + bbox::BoundingBox + attr::KW # store label, width, and height for initialization + # label # this is the label that the subplot will take (since we create a layout before initialization) +end +EmptyLayout(parent = RootLayout(); kw...) = EmptyLayout(parent, DEFAULT_BBOX[], KW(kw)) + +Base.size(layout::EmptyLayout) = (0, 0) +Base.length(layout::EmptyLayout) = 0 +Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing + +# ----------------------------------------------------------- +# GridLayout + +# nested, gridded layout with optional size percentages +mutable struct GridLayout <: AbstractLayout + parent::AbstractLayout + minpad::Tuple # leftpad, toppad, rightpad, bottompad + bbox::BoundingBox + grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion + widths::Vector{Measure} + heights::Vector{Measure} + attr::KW +end + +leftpad(layout::GridLayout) = leftpad(layout.minpad) +toppad(layout::GridLayout) = toppad(layout.minpad) +rightpad(layout::GridLayout) = rightpad(layout.minpad) +bottompad(layout::GridLayout) = bottompad(layout.minpad) + +function GridLayout( + dims...; + parent = RootLayout(), + widths = zeros(dims[2]), + heights = zeros(dims[1]), + kw..., +) + grid = Matrix{AbstractLayout}(undef, dims...) + layout = GridLayout( + parent, + DEFAULT_MINPAD[], + DEFAULT_BBOX[], + grid, + Measure[w * pct for w in widths], + Measure[h * pct for h in heights], + # convert(Vector{Float64}, widths), + # convert(Vector{Float64}, heights), + KW(kw), + ) + for i in eachindex(grid) + grid[i] = EmptyLayout(layout) + end + layout +end + +Base.size(layout::GridLayout) = size(layout.grid) +Base.length(layout::GridLayout) = length(layout.grid) +Base.getindex(layout::GridLayout, r::Int, c::Int) = layout.grid[r, c] +Base.setindex!(layout::GridLayout, v, r::Int, c::Int) = layout.grid[r, c] = v +Base.setindex!(layout::GridLayout, v, ci::CartesianIndex) = layout.grid[ci] = v diff --git a/PlotsBase/src/Commons/measures.jl b/PlotsBase/src/Commons/measures.jl new file mode 100644 index 000000000..127991391 --- /dev/null +++ b/PlotsBase/src/Commons/measures.jl @@ -0,0 +1,75 @@ + +const DEFAULT_BBOX = Ref(BoundingBox(0mm, 0mm, 0mm, 0mm)) +const DEFAULT_MINPAD = Ref((20mm, 5mm, 2mm, 10mm)) +const DEFAULT_LINEWIDTH = Ref(1) +const PLOTS_SEED = 1234 +const PX_PER_INCH = 100 +const DPI = PX_PER_INCH +const MM_PER_INCH = 25.4 +const MM_PER_PX = MM_PER_INCH / PX_PER_INCH +const _cbar_width = 5mm + +# allow pixels and percentages +const px = Measures.AbsoluteLength(0.254) +const pct = Measures.Length{:pct,Float64}(1.0) + +const BBox = Measures.Absolute2DBox + +to_pixels(m::AbsoluteLength) = m.value / px.value + +# convert x,y coordinates from absolute coords to percentages... +# returns x_pct, y_pct +function xy_mm_to_pcts(x::AbsoluteLength, y::AbsoluteLength, figw, figh, flipy = true) + xmm, ymm = x.value, y.value + if flipy + ymm = figh.value - ymm # flip y when origin in bottom-left + end + xmm / figw.value, ymm / figh.value +end + +# convert a bounding box from absolute coords to percentages... +# returns an array of percentages of figure size: [left, bottom, width, height] +function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true) + mms = Float64[f(bb).value for f in (left, bottom, width, height)] + if flipy + mms[2] = figh.value - mms[2] # flip y when origin in bottom-left + end + mms ./ Float64[figw.value, figh.value, figw.value, figh.value] +end + +Base.show(io::IO, bbox::BoundingBox) = print( + io, + "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}", +) + +# Base.:*{T,N}(m1::Length{T,N}, m2::Length{T,N}) = Length{T,N}(m1.value * m2.value) +ispositive(m::Measure) = m.value > 0 + +# union together bounding boxes +function Base.:+(bb1::BoundingBox, bb2::BoundingBox) + # empty boxes don't change the union + ispositive(width(bb1)) || return bb2 + ispositive(height(bb1)) || return bb2 + ispositive(width(bb2)) || return bb1 + ispositive(height(bb2)) || return bb1 + + l = min(left(bb1), left(bb2)) + t = min(top(bb1), top(bb2)) + r = max(right(bb1), right(bb2)) + b = max(bottom(bb1), bottom(bb2)) + BoundingBox(l, t, r - l, b - t) +end + +Base.convert(::Type{<:Measure}, x::Float64) = x * pct + +Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value) +Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value) +Base.:/(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value) +Base.:/(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value) + +inch2px(inches::Real) = float(inches * PX_PER_INCH) +px2inch(px::Real) = float(px / PX_PER_INCH) +inch2mm(inches::Real) = float(inches * MM_PER_INCH) +mm2inch(mm::Real) = float(mm / MM_PER_INCH) +px2mm(px::Real) = float(px * MM_PER_PX) +mm2px(mm::Real) = float(mm / MM_PER_PX) diff --git a/PlotsBase/src/Commons/postprocess_attrs.jl b/PlotsBase/src/Commons/postprocess_attrs.jl index f6cfad022..67e48da7f 100644 --- a/PlotsBase/src/Commons/postprocess_attrs.jl +++ b/PlotsBase/src/Commons/postprocess_attrs.jl @@ -1,21 +1,18 @@ # add all pluralized forms to the _keyAliases dict -for arg in _all_attrs - add_aliases(arg, makeplural(arg)) -end +foreach(arg -> add_aliases(arg, makeplural(arg)), _all_attrs) # fill symbol cache for letter in (:x, :y, :z) - _attrsymbolcache[letter] = Dict{Symbol,Symbol}() - for k in _axis_attrs + new_attr_dict!(letter) + for keyword in _axis_attrs # populate attribute cache - lk = Symbol(letter, k) - _attrsymbolcache[letter][k] = lk - # allow the underscore version too: xguide or x_guide - add_aliases(lk, Symbol(letter, "_", k)) + letter_keyword = set_attr_symbol!(letter, string(keyword)) + # allow the underscore version too: `xguide` or `x_guide` + add_aliases(letter_keyword, Symbol(letter, "_", keyword)) end - for k in (_magic_axis_attrs..., :(_discrete_indices)) - _attrsymbolcache[letter][k] = Symbol(letter, k) + for keyword in (_magic_axis_attrs..., :(_discrete_indices)) + _attrsymbolcache[letter][keyword] = Symbol(letter, keyword) end end diff --git a/PlotsBase/src/Series.jl b/PlotsBase/src/DataSeries.jl similarity index 92% rename from PlotsBase/src/Series.jl rename to PlotsBase/src/DataSeries.jl index 411e82549..17043f0d0 100644 --- a/PlotsBase/src/Series.jl +++ b/PlotsBase/src/DataSeries.jl @@ -1,4 +1,4 @@ -module PlotsSeries +module DataSeries export Series, should_add_to_legend, @@ -20,11 +20,13 @@ export get_linestyle, get_fillalpha, get_markercolor, get_markeralpha -import PlotsBase.Commons: get_subplot, _series_defaults -using PlotsBase.Commons -using PlotsBase.Commons: get_gradient -using PlotsBase.PlotUtils: ColorGradient, plot_color -using PlotsBase: PlotsBase, DefaultsDict, RecipesPipeline, get_attr_symbol, KW + +import ..Commons: get_gradient, get_subplot, _series_defaults +import ..PlotsBase + +using ..PlotsBase: DefaultsDict, RecipesPipeline, get_attr_symbol, KW +using ..PlotUtils: ColorGradient, plot_color +using ..Commons mutable struct Series plotattributes::DefaultsDict @@ -36,23 +38,6 @@ 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...) -# TODO: consider removing -attr(series::Series, k::Symbol) = series.plotattributes[k] -attr!(series::Series, v, k::Symbol) = (series.plotattributes[k] = v) -function attr!(series::Series; kw...) - plotattributes = KW(kw) - PlotsBase.Commons.preprocess_attributes!(plotattributes) - for (k, v) in plotattributes - if haskey(_series_defaults, k) - series[k] = v - else - @warn "unused key $k in series attr" - end - end - PlotsBase._series_updated(series[:subplot].plt, series) - series -end - should_add_to_legend(series::Series) = series.plotattributes[:primary] && series.plotattributes[:label] != "" && @@ -103,7 +88,7 @@ end function copy_series!(series, letter) plt = series[:plot_object] for s in plt.series_list, l in (:x, :y, :z) - if (s !== series || l !== letter) && s[l] === series[letter] + if (s ≢ series || l ≢ letter) && s[l] ≡ series[letter] series[letter] = copy(series[letter]) end end @@ -137,11 +122,11 @@ for comp in (:line, :fill, :marker) ) c = series[$Symbol($compcolor)] # series[:linecolor], series[:fillcolor], series[:markercolor] z = series[$Symbol($comp_z)] # series[:line_z], series[:fill_z], series[:marker_z] - if z === nothing + if z ≡ nothing isa(c, ColorGradient) ? c : plot_color(_cycle(c, i)) else grad = get_gradient(c) - if s === :identity + if s ≡ :identity get(grad, z[i], (cmin, cmax)) else base = _log_scale_bases[s] @@ -151,7 +136,7 @@ for comp in (:line, :fill, :marker) end function $get_compcolor(series, i::Integer = 1, s::Symbol = :identity) - if series[$Symbol($comp_z)] === nothing + if series[$Symbol($comp_z)] ≡ nothing $get_compcolor(series, 0, 1, i, s) else $get_compcolor(series, get_clims(series[:subplot]), i, s) @@ -182,17 +167,17 @@ function get_colorgradient(series::Series) series[:fillcolor] elseif st in (:contour, :wireframe, :contour3d) series[:linecolor] - elseif series[:marker_z] !== nothing + elseif series[:marker_z] ≢ nothing series[:markercolor] - elseif series[:line_z] !== nothing + elseif series[:line_z] ≢ nothing series[:linecolor] - elseif series[:fill_z] !== nothing + elseif series[:fill_z] ≢ nothing series[:fillcolor] end end iscontour(series::Series) = series[:seriestype] in (:contour, :contour3d) -isfilledcontour(series::Series) = iscontour(series) && series[:fillrange] !== nothing +isfilledcontour(series::Series) = iscontour(series) && series[:fillrange] ≢ nothing function contour_levels(series::Series, clims) iscontour(series) || error("Not a contour series") @@ -225,12 +210,12 @@ struct NaNSegmentsIterator end function Base.iterate(itr::NaNSegmentsIterator, nextidx::Int = itr.n1) - (i = findfirst(!PlotsBase.Commons.anynan(itr.args), nextidx:(itr.n2))) === nothing && + (i = findfirst(!PlotsBase.Commons.anynan(itr.args), nextidx:(itr.n2))) ≡ nothing && return nextval = nextidx + i - 1 j = findfirst(PlotsBase.Commons.anynan(itr.args), nextval:(itr.n2)) - nextnan = j === nothing ? itr.n2 + 1 : nextval + j - 1 + nextnan = j ≡ nothing ? itr.n2 + 1 : nextval + j - 1 nextval:(nextnan - 1), nextnan end @@ -258,7 +243,7 @@ has_attribute_segments(series::Series) = function series_segments(series::Series, seriestype::Symbol = :path; check = false) x, y, z = series[:x], series[:y], series[:z] - (x === nothing || isempty(x)) && return UnitRange{Int}[] + (x ≡ nothing || isempty(x)) && return UnitRange{Int}[] args = RecipesPipeline.is3d(series) ? (x, y, z) : (x, y) nan_segments = collect(iter_segments(args...)) @@ -280,7 +265,7 @@ function series_segments(series::Series, seriestype::Symbol = :path; check = fal segments = if has_attribute_segments(series) map(nan_segments) do r - if seriestype === :shape + if seriestype ≡ :shape warn_on_inconsistent_shape_attrs(series, x, y, z, r) (SeriesSegment(r, first(r)),) elseif seriestype in (:scatter, :scatter3d) @@ -329,4 +314,26 @@ function warn_on_inconsistent_shape_attrs(series, x, y, z, r) end end end -end # PlotsSeries + +end # module + +# ------------------------------------------------------------------- + +using .DataSeries + +# TODO: consider removing +attr(series::Series, k::Symbol) = series.plotattributes[k] +attr!(series::Series, v, k::Symbol) = (series.plotattributes[k] = v) +function attr!(series::Series; kw...) + plotattributes = KW(kw) + Commons.preprocess_attributes!(plotattributes) + for (k, v) in plotattributes + if haskey(_series_defaults, k) + series[k] = v + else + @warn "unused key $k in series attr" + end + end + _series_updated(series[:subplot].plt, series) + series +end diff --git a/PlotsBase/src/Fonts.jl b/PlotsBase/src/Fonts.jl index 965c15611..2434bc018 100644 --- a/PlotsBase/src/Fonts.jl +++ b/PlotsBase/src/Fonts.jl @@ -1,11 +1,12 @@ module Fonts -using PlotsBase.Colors -using PlotsBase.Commons -using PlotsBase.Commons: +using ..Colors +using ..Commons +using ..Commons: _initial_plt_fontsizes, _initial_sp_fontsizes, _initial_ax_fontsizes, _initial_fontsizes + # keep in mind: these will be reexported and are public API -export font, scalefontsizes, resetfontsizes, text, is_horizontal, Font, PlotText +export Font, PlotText, font, scalefontsizes, resetfontsizes, text, is_horizontal mutable struct Font family::AbstractString @@ -44,7 +45,7 @@ function font(args...; kw...) for arg in args T = typeof(arg) - @assert arg !== :match + @assert arg ≢ :match if T == Font family = arg.family @@ -53,7 +54,7 @@ function font(args...; kw...) valign = arg.valign rotation = arg.rotation color = arg.color - elseif arg === :center + elseif arg ≡ :center halign = :hcenter valign = :vcenter elseif arg ∈ _haligns @@ -78,21 +79,21 @@ function font(args...; kw...) end for sym in keys(kw) - if sym === :family + if sym ≡ :family family = string(kw[sym]) - elseif sym === :pointsize + elseif sym ≡ :pointsize pointsize = kw[sym] - elseif sym === :halign + elseif sym ≡ :halign halign = kw[sym] - halign === :center && (halign = :hcenter) + halign ≡ :center && (halign = :hcenter) @assert halign ∈ _haligns - elseif sym === :valign + elseif sym ≡ :valign valign = kw[sym] - valign === :center && (valign = :vcenter) + valign ≡ :center && (valign = :vcenter) @assert valign ∈ _valigns - elseif sym === :rotation + elseif sym ≡ :rotation rotation = kw[sym] - elseif sym === :color + elseif sym ≡ :color col = kw[sym] color = col isa Colorant ? col : parse(Colorant, col) else @@ -174,4 +175,9 @@ text(str, args...; kw...) = PlotText(string(str), font(args...; kw...)) Base.length(t::PlotText) = length(t.str) is_horizontal(t::PlotText) = abs(sind(t.font.rotation)) ≤ sind(45) -end # Fonts + +end # module + +# ------------------------------------------------------------------- + +@reexport using .Fonts diff --git a/PlotsBase/src/PlotMeasures.jl b/PlotsBase/src/PlotMeasures.jl deleted file mode 100644 index bcbc843ef..000000000 --- a/PlotsBase/src/PlotMeasures.jl +++ /dev/null @@ -1,40 +0,0 @@ -module PlotMeasures - -export PX_PER_INCH, - DPI, MM_PER_INCH, MM_PER_PX, DEFAULT_BBOX, DEFAULT_MINPAD, DEFAULT_LINEWIDTH - -import ..Measures -import ..Measures: - Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height, w, h - -const BBox = Measures.Absolute2DBox -export BBox, BoundingBox, mm, cm, inch, px, pct, pt, w, h - -# allow pixels and percentages -const px = AbsoluteLength(0.254) -const pct = Length{:pct,Float64}(1.0) - -const PX_PER_INCH = 100 -const DPI = PX_PER_INCH -const MM_PER_INCH = 25.4 -const MM_PER_PX = MM_PER_INCH / PX_PER_INCH -const _cbar_width = 5mm -const DEFAULT_BBOX = Ref(BoundingBox(0mm, 0mm, 0mm, 0mm)) -const DEFAULT_MINPAD = Ref((20mm, 5mm, 2mm, 10mm)) -const DEFAULT_LINEWIDTH = Ref(1) - -Base.convert(::Type{<:Measure}, x::Float64) = x * pct - -Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value) -Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value) -Base.:/(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value) -Base.:/(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value) - -inch2px(inches::Real) = float(inches * PX_PER_INCH) -px2inch(px::Real) = float(px / PX_PER_INCH) -inch2mm(inches::Real) = float(inches * MM_PER_INCH) -mm2inch(mm::Real) = float(mm / MM_PER_INCH) -px2mm(px::Real) = float(px * MM_PER_PX) -mm2px(mm::Real) = float(mm / MM_PER_PX) - -end diff --git a/PlotsBase/src/PlotsPlots.jl b/PlotsBase/src/Plots.jl similarity index 81% rename from PlotsBase/src/PlotsPlots.jl rename to PlotsBase/src/Plots.jl index 25a561201..7eae830ec 100644 --- a/PlotsBase/src/PlotsPlots.jl +++ b/PlotsBase/src/Plots.jl @@ -1,33 +1,29 @@ -module PlotsPlots +module Plots export Plot, PlotOrSubplot, - _update_plot_attrs, plottitlefont, ignorenan_extrema, - protect, - InputWrapper -import PlotsBase.Axes: _update_axis, scale_lims! -import PlotsBase.Commons: ignorenan_extrema, _cycle -import PlotsBase.Ticks: get_ticks -using PlotsBase: - PlotsBase, - AbstractPlot, - AbstractBackend, - DefaultsDict, - Series, - AbstractLayout, - RecipesPipeline -using PlotsBase.PlotMeasures -using PlotsBase.Colorbars: _update_subplot_colorbars -using PlotsBase.Subplots: Subplot, _update_subplot_colors, _update_margins -using PlotsBase.Axes: Axis, get_axis -using PlotsBase.PlotUtils: get_color_palette -using PlotsBase.Commons -using PlotsBase.Commons.Frontend -using PlotsBase.Fonts: font + _update_plot_attrs, + InputWrapper, + protect + +import ..RecipesBase: AbstractLayout, AbstractBackend, AbstractPlot +import ..RecipesPipeline: RecipesPipeline, DefaultsDict +import ..Subplots: Subplot, _update_subplot_colors, _update_margins +import ..Colorbars: _update_subplot_colorbars +import ..Commons: ignorenan_extrema, _cycle + +using ..PlotUtils +using ..DataSeries +using ..Commons.Frontend +using ..Commons +using ..Fonts +using ..Ticks +using ..Axes const SubplotMap = Dict{Any,Subplot} + mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T} backend::T # the backend type n::Int # number of series @@ -50,7 +46,7 @@ mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T} nothing, Subplot[], SubplotMap(), - PlotsBase.EmptyLayout(), + EmptyLayout(), Subplot[], false, ) @@ -62,14 +58,14 @@ mutable struct Plot{T<:AbstractBackend} <: AbstractPlot{T} sp = deepcopy(osp) # FIXME: fails `PlotlyJS` ? plt.layout.grid[1, 1] = sp # reset some attributes - sp.minpad = PlotMeasures.DEFAULT_MINPAD[] - sp.bbox = PlotMeasures.DEFAULT_BBOX[] - sp.plotarea = PlotMeasures.DEFAULT_BBOX[] + sp.minpad = DEFAULT_MINPAD[] + sp.bbox = DEFAULT_BBOX[] + sp.plotarea = DEFAULT_BBOX[] sp.plt = plt # change the enclosing plot push!(plt.subplots, sp) plt end -end # Plot +end const PlotOrSubplot = Union{Plot,Subplot} # ----------------------------------------------------------- @@ -138,16 +134,16 @@ end # --------------------------------------------------------------- -"Smallest x in plot" +"smallest x in plot" xmin(plt::Plot) = ignorenan_minimum([ ignorenan_minimum(series.plotattributes[:x]) for series in plt.series_list ]) -"Largest x in plot" +"largest x in plot" xmax(plt::Plot) = ignorenan_maximum([ ignorenan_maximum(series.plotattributes[:x]) for series in plt.series_list ]) -"Extrema of x-values in plot" +"extrema of x-values in plot" ignorenan_extrema(plt::Plot) = (xmin(plt), xmax(plt)) # --------------------------------------------------------------- @@ -155,7 +151,7 @@ ignorenan_extrema(plt::Plot) = (xmin(plt), xmax(plt)) # properly retrieve from plt.attr, passing `:match` to the correct key Base.getindex(plt::Plot, k::Symbol) = - if (v = plt.attr[k]) === :match + if (v = plt.attr[k]) ≡ :match plt[Commons._match_map[k]] else v @@ -175,15 +171,15 @@ Base.ndims(plt::Plot) = 2 # clear out series list, but retain subplots Base.empty!(plt::Plot) = foreach(sp -> empty!(sp.series_list), plt.subplots) -PlotsBase.get_subplot(plt::Plot, sp::Subplot) = sp -PlotsBase.get_subplot(plt::Plot, i::Integer) = plt.subplots[i] -PlotsBase.get_subplot(plt::Plot, k) = plt.spmap[k] -PlotsBase.series_list(plt::Plot) = plt.series_list +Commons.get_subplot(plt::Plot, sp::Subplot) = sp +Commons.get_subplot(plt::Plot, i::Integer) = plt.subplots[i] +Commons.get_subplot(plt::Plot, k) = plt.spmap[k] +Commons.series_list(plt::Plot) = plt.series_list -get_ticks(p::Plot, s::Symbol) = map(sp -> get_ticks(sp, s), p.subplots) +Commons.get_ticks(p::Plot, s::Symbol) = map(sp -> get_ticks(sp, s), p.subplots) -get_subplot_index(plt::Plot, sp::Subplot) = findfirst(x -> x === sp, plt.subplots) -PlotsBase.RecipesPipeline.preprocess_attributes!(plt::Plot, plotattributes::AKW) = +get_subplot_index(plt::Plot, sp::Subplot) = findfirst(x -> x ≡ sp, plt.subplots) +RecipesPipeline.preprocess_attributes!(plt::Plot, plotattributes::AKW) = Commons.preprocess_attributes!(plotattributes) plottitlefont(p::Plot) = font(; @@ -218,7 +214,7 @@ function _update_axis_links(plt::Plot, axis::Axis, letter::Symbol) nothing end -function PlotsBase.Axes._update_axis( +function Axes._update_axis( plt::Plot, sp::Subplot, plotattributes_in::AKW, @@ -228,14 +224,14 @@ function PlotsBase.Axes._update_axis( # get (maybe initialize) the axis axis = get_axis(sp, letter) - _update_axis(axis, plotattributes_in, letter, subplot_index) + Axes._update_axis(axis, plotattributes_in, letter, subplot_index) # convert a bool into auto or nothing if isa(axis[:ticks], Bool) axis[:ticks] = axis[:ticks] ? :auto : nothing end - PlotsBase.Axes._update_axis_colors(axis) + Axes._update_axis_colors(axis) _update_axis_links(plt, axis, letter) nothing end @@ -265,7 +261,7 @@ function _update_subplot_attrs( lims_warned = false for letter in (:x, :y, :z) - _update_axis(plt, sp, plotattributes_in, letter, subplot_index) + Axes._update_axis(plt, sp, plotattributes_in, letter, subplot_index) lk = get_attr_symbol(letter, :lims) # warn against using `Range` in x,y,z lims @@ -280,14 +276,19 @@ function _update_subplot_attrs( PlotsBase.Subplots._update_subplot_periphery(sp, anns) end -function scale_lims!(plt::Plot, letter, factor) +function Commons.scale_lims!(plt::Plot, letter, factor) foreach(sp -> scale_lims!(sp, letter, factor), plt.subplots) plt end -function scale_lims!(plt::Union{Plot,Subplot}, factor) +function Commons.scale_lims!(plt::Union{Plot,Subplot}, factor) foreach(letter -> scale_lims!(plt, letter, factor), (:x, :y, :z)) plt end Commons.get_size(plt::Plot) = get_size(plt.attr) Commons.get_thickness_scaling(plt::Plot) = get_thickness_scaling(plt.attr) -end # PlotsPlots + +end # module + +# ------------------------------------------------------------------- + +using .Plots diff --git a/PlotsBase/src/PlotsBase.jl b/PlotsBase/src/PlotsBase.jl index c78fef6f6..09ad15f27 100644 --- a/PlotsBase/src/PlotsBase.jl +++ b/PlotsBase/src/PlotsBase.jl @@ -8,7 +8,7 @@ if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@max_m end using Pkg, Dates, Printf, Statistics, Base64, LinearAlgebra, SparseArrays, Random -using Reexport, RelocatableFolders +using PrecompileTools, Preferences, Reexport, RelocatableFolders using Base.Meta @reexport using RecipesBase @reexport using PlotThemes @@ -37,6 +37,7 @@ import RecipesPipeline: import UnicodeFun import StatsBase import Downloads +import Measures import Showoff import Unzip import JLFzf @@ -115,72 +116,37 @@ export plotattr, scalefontsizes, resetfontsizes + #! format: on -import Measures -include("PlotMeasures.jl") -using .PlotMeasures -import .PlotMeasures: Length, AbsoluteLength, Measure, width, height -# --------------------------------------------------------- -macro ScopeModule(mod::Symbol, parent::Symbol, symbols...) - Expr( - :module, - true, - mod, - Expr( - :block, - Expr( - :import, - Expr( - :(:), - Expr(:., :., :., parent), - (Expr(:., s isa Expr ? s.args[1] : s) for s in symbols)..., - ), - ), - Expr(:export, (s isa Expr ? s.args[1] : s for s in symbols)...), - ), - ) |> esc -end import NaNMath + +const _project = Pkg.Types.read_package(normpath(@__DIR__, "..", "Project.toml")) +const _version = _project.version +const _compat = _project.compat + include("Commons/Commons.jl") using .Commons using .Commons.Frontend -# --------------------------------------------------------- + +Commons.@generic_functions attr attr! rotate rotate! + include("Fonts.jl") -@reexport using .Fonts -using .Fonts: Font, PlotText include("Ticks.jl") -using .Ticks -include("Series.jl") -using .PlotsSeries +include("DataSeries.jl") include("Subplots.jl") -using .Subplots -import .Subplots: plotarea, plotarea!, leftpad, toppad, bottompad, rightpad include("Axes.jl") -using .Axes include("Surfaces.jl") include("Colorbars.jl") -using .Colorbars -include("PlotsPlots.jl") -using .PlotsPlots +include("Plots.jl") include("layouts.jl") -# --------------------------------------------------------- include("utils.jl") -using .Surfaces include("axes_utils.jl") include("legend.jl") include("Shapes.jl") -using .Shapes -using .Shapes: Shape, _shapes, rotate! include("Annotations.jl") -using .Annotations -using .Annotations: SeriesAnnotations, process_annotation include("Arrows.jl") -using .Arrows include("Strokes.jl") -using .Strokes -using .Strokes: Stroke, Brush include("BezierCurves.jl") -using .BezierCurves include("themes.jl") include("plot.jl") include("pipeline.jl") @@ -189,16 +155,45 @@ include("recipes.jl") include("animation.jl") include("examples.jl") include("plotattr.jl") -include("backends/nobackend.jl") -include("abstract_backend.jl") include("alignment.jl") -const CURRENT_BACKEND = CurrentBackend(:none) include("output.jl") include("shorthands.jl") -include("backends/web.jl") -include("backends/plotly.jl") -using .Plotly +include("backends.jl") +include("web.jl") +include("plotly.jl") +include("preferences.jl") include("init.jl") include("users.jl") +# COV_EXCL_START +@setup_workload begin + backend(:none) + n = length(_examples) + imports = sizehint!(Expr[], n) + examples = sizehint!(Expr[], 10n) + scratch_dir = mktempdir() + for i in setdiff(1:n, _backend_skips[backend_name()], _animation_examples) + _examples[i].external && continue + (imp = _examples[i].imports) ≡ nothing || push!(imports, imp) + func = gensym(string(i)) + push!(examples, quote + $func() = begin # evaluate each example in a local scope + $(_examples[i].exprs) + $i == 1 || return # trigger display only for one example + fn = joinpath(scratch_dir, tempname()) + show(devnull, current()) + nothing + end + $func() + end) + end + @compile_workload begin + backend(:none) + eval.(imports) + eval.(examples) + end + CURRENT_PLOT.nullableplot = nothing +end +# COV_EXCL_STOP + end diff --git a/PlotsBase/src/Shapes.jl b/PlotsBase/src/Shapes.jl index 81b412ec0..2c8b9e55e 100644 --- a/PlotsBase/src/Shapes.jl +++ b/PlotsBase/src/Shapes.jl @@ -1,7 +1,9 @@ module Shapes -using PlotsBase: PlotsBase, RecipesPipeline -using PlotsBase.Commons +import ..PlotsBase + +using ..RecipesPipeline +using ..Commons # keep in mind: these will be reexported and are public API export Shape, @@ -16,9 +18,7 @@ export Shape, scale!, scale, translate, - translate!, - rotate, - rotate! + translate! const P2 = NTuple{2,Float64} const P3 = NTuple{3,Float64} @@ -205,13 +205,20 @@ rotate_x(x::Real, y::Real, θ::Real, centerx::Real, centery::Real) = rotate_y(x::Real, y::Real, θ::Real, centerx::Real, centery::Real) = ((y - centery) * cos(θ) + (x - centerx) * sin(θ) + centery) -rotate(x::Real, y::Real, θ::Real, c) = (rotate_x(x, y, θ, c...), rotate_y(x, y, θ, c...)) +end # module + +# ------------------------------------------------------------------- + +using .Shapes + +rotate(x::Real, y::Real, θ::Real, c) = + (Shapes.rotate_x(x, y, θ, c...), Shapes.rotate_y(x, y, θ, c...)) function rotate!(shape::Shape, θ::Real, c = center(shape)) x, y = coords(shape) for i in eachindex(x) - xi = rotate_x(x[i], y[i], θ, c...) - yi = rotate_y(x[i], y[i], θ, c...) + xi = Shapes.rotate_x(x[i], y[i], θ, c...) + yi = Shapes.rotate_y(x[i], y[i], θ, c...) x[i], y[i] = xi, yi end shape @@ -220,9 +227,7 @@ end "rotate an object in space" function rotate(shape::Shape, θ::Real, c = center(shape)) x, y = coords(shape) - x_new = rotate_x.(x, y, θ, c...) - y_new = rotate_y.(x, y, θ, c...) + x_new = Shapes.rotate_x.(x, y, θ, c...) + y_new = Shapes.rotate_y.(x, y, θ, c...) Shape(x_new, y_new) end - -end # Shapes diff --git a/PlotsBase/src/Strokes.jl b/PlotsBase/src/Strokes.jl index 5fcb8a5b3..840f42a55 100644 --- a/PlotsBase/src/Strokes.jl +++ b/PlotsBase/src/Strokes.jl @@ -1,8 +1,10 @@ module Strokes -export stroke, brush, Stroke, Brush -using PlotsBase.Colors: Colorant -using PlotsBase.Commons: all_alphas, all_reals, all_styles +export Stroke, Brush, stroke, brush + +using ..Colors: Colorant +using ..Commons: all_alphas, all_reals, all_styles + struct Stroke width color @@ -77,6 +79,8 @@ function brush(args...; alpha = nothing) Brush(size, color, alpha) end -# ----------------------------------------------------------------------- +end # module + +# ------------------------------------------------------------------- -end # Strokes +using .Strokes diff --git a/PlotsBase/src/Subplots.jl b/PlotsBase/src/Subplots.jl index 3abbf6fc0..c41b4c1b2 100644 --- a/PlotsBase/src/Subplots.jl +++ b/PlotsBase/src/Subplots.jl @@ -6,29 +6,19 @@ export Subplot, legendtitlefont, titlefont, get_series_color, - needs_any_3d_axes, - plotarea, - plotarea!, - toppad, - leftpad, - bottompad, - rightpad -import PlotsBase.Ticks: get_ticks -using PlotsBase: - PlotsBase, - RecipesPipeline, - Series, - AbstractBackend, - AbstractLayout, - BoundingBox, - DefaultsDict -using PlotsBase.RecipesPipeline: RecipesPipeline, Surface, Volume -using PlotsBase.PlotUtils: get_color_palette -using PlotsBase.Commons -using PlotsBase.Commons.Frontend -using PlotsBase.Commons: convert_legend_value, like_surface -using PlotsBase.Fonts -using PlotsBase.PlotMeasures + needs_any_3d_axes +import PlotsBase + +import ..Commons: BoundingBox, convert_legend_value, like_surface +import ..RecipesPipeline: RecipesPipeline, Surface, Volume, DefaultsDict +import ..RecipesBase: AbstractLayout, AbstractBackend +import ..DataSeries: Series +import ..PlotUtils + +using ..Commons.Frontend +using ..Commons +using ..Fonts +using ..Ticks # a single subplot mutable struct Subplot{T<:AbstractBackend} <: AbstractLayout @@ -42,7 +32,7 @@ mutable struct Subplot{T<:AbstractBackend} <: AbstractLayout o # can store backend-specific data... like a pyplot ax plt # the enclosing Plot object (can't give it a type because of no forward declarations) - Subplot(::T; parent = PlotsBase.RootLayout()) where {T<:AbstractBackend} = new{T}( + Subplot(::T; parent = RootLayout()) where {T<:AbstractBackend} = new{T}( parent, Series[], 0, @@ -57,9 +47,9 @@ end # properly retrieve from sp.attr, passing `:match` to the correct key Base.getindex(sp::Subplot, k::Symbol) = - if (v = sp.attr[k]) === :match - if haskey(Commons.Commons._match_map2, k) - sp.plt[Commons.Commons._match_map2[k]] + if (v = sp.attr[k]) ≡ :match + if haskey(Commons._match_map2, k) + sp.plt[Commons._match_map2[k]] else sp[Commons._match_map[k]] end @@ -82,41 +72,23 @@ Base.show(io::IO, sp::Subplot) = print(io, "Subplot{$(sp[:subplot_index])}") Return the bounding box of a subplot. """ -plotarea(sp::Subplot) = sp.plotarea -plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox) +Commons.plotarea(sp::Subplot) = sp.plotarea +Commons.plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox) Base.size(sp::Subplot) = (1, 1) Base.length(sp::Subplot) = 1 Base.getindex(sp::Subplot, r::Int, c::Int) = sp -leftpad(sp::Subplot) = sp.minpad[1] -toppad(sp::Subplot) = sp.minpad[2] -rightpad(sp::Subplot) = sp.minpad[3] -bottompad(sp::Subplot) = sp.minpad[4] - -function attr!(sp::Subplot; kw...) - plotattributes = KW(kw) - PlotsBase.Commons.preprocess_attributes!(plotattributes) - for (k, v) in plotattributes - if haskey(_subplot_defaults, k) - sp[k] = v - else - @warn "unused key $k in subplot attr" - end - end - sp -end - -PlotsBase.series_list(sp::Subplot) = sp.series_list # filter(series -> series.plotattributes[:subplot] === sp, sp.plt.series_list) -PlotsBase.RecipesPipeline.is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d" +RecipesPipeline.is3d(sp::Subplot) = string(sp.attr[:projection]) == "3d" +PlotsBase.series_list(sp::Subplot) = sp.series_list # filter(series -> series.plotattributes[:subplot] ≡ sp, sp.plt.series_list) PlotsBase.ispolar(sp::Subplot) = string(sp.attr[:projection]) == "polar" -get_ticks(sp::Subplot, s::Symbol) = get_ticks(sp, sp[get_attr_symbol(s, :axis)]) +Commons.get_ticks(sp::Subplot, s::Symbol) = get_ticks(sp, sp[get_attr_symbol(s, :axis)]) # 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 + if c ≡ :auto like_surface(seriestype) ? PlotsBase.cgrad() : _cycle(sp[:color_palette], n) elseif isa(c, Int) _cycle(sp[:color_palette], c) @@ -174,7 +146,7 @@ function _update_subplot_periphery(sp::Subplot, anns::AVec) # handle legend/colorbar sp.attr[:legend_position] = convert_legend_value(sp.attr[:legend_position]) sp.attr[:colorbar] = convert_legend_value(sp.attr[:colorbar]) - if sp.attr[:colorbar] === :legend + if sp.attr[:colorbar] ≡ :legend sp.attr[:colorbar] = sp.attr[:legend_position] end nothing @@ -183,7 +155,7 @@ end function _update_subplot_colors(sp::Subplot) # background colors color_or_nothing!(sp.attr, :background_color_subplot) - sp.attr[:color_palette] = get_color_palette(sp.attr[:color_palette], 30) + sp.attr[:color_palette] = PlotUtils.get_color_palette(sp.attr[:color_palette], 30) color_or_nothing!(sp.attr, :legend_background_color) color_or_nothing!(sp.attr, :background_color_inside) @@ -214,9 +186,9 @@ function PlotsBase.expand_extrema!(sp::Subplot, plotattributes::AKW) for letter in (:x, :y, :z) data = plotattributes[letter] if ( - letter !== :z && - plotattributes[:seriestype] === :straightline && - any(series[:seriestype] !== :straightline for series in series_list(sp)) && + letter ≢ :z && + plotattributes[:seriestype] ≡ :straightline && + any(series[:seriestype] ≢ :straightline for series in series_list(sp)) && length(data) > 1 && data[1] != data[2] ) @@ -235,7 +207,7 @@ function PlotsBase.expand_extrema!(sp::Subplot, plotattributes::AKW) data = plotattributes[letter] = Surface(Matrix{Float64}(data.surf)) end expand_extrema!(axis, data) - elseif data !== nothing + elseif data ≢ nothing # TODO: need more here... gotta track the discrete reference value # as well as any coord offset (think of boxplot shape coords... they all # correspond to the same x-value) @@ -248,10 +220,10 @@ function PlotsBase.expand_extrema!(sp::Subplot, plotattributes::AKW) # expand for fillrange fr = plotattributes[:fillrange] - if fr === nothing && plotattributes[:seriestype] === :bar + if fr ≡ nothing && plotattributes[:seriestype] ≡ :bar fr = 0.0 end - if fr !== nothing && !RecipesPipeline.is3d(plotattributes) + if fr ≢ nothing && !RecipesPipeline.is3d(plotattributes) axis = sp.attr[:yaxis] if typeof(fr) <: Tuple foreach(x -> expand_extrema!(axis, x), fr) @@ -261,11 +233,11 @@ function PlotsBase.expand_extrema!(sp::Subplot, plotattributes::AKW) end # expand for bar_width - if plotattributes[:seriestype] === :bar + if plotattributes[:seriestype] ≡ :bar dsym = :x data = plotattributes[dsym] - if (bw = plotattributes[:bar_width]) === nothing + if (bw = plotattributes[:bar_width]) ≡ nothing pos = filter(>(0), diff(sort(data))) plotattributes[:bar_width] = bw = Commons._bar_width * ignorenan_minimum(pos) end @@ -275,7 +247,7 @@ function PlotsBase.expand_extrema!(sp::Subplot, plotattributes::AKW) end # expand for heatmaps - if plotattributes[:seriestype] === :heatmap + if plotattributes[:seriestype] ≡ :heatmap for letter in (:x, :y) data = plotattributes[letter] axis = sp[get_attr_symbol(letter, :axis)] @@ -290,6 +262,29 @@ function PlotsBase.expand_extrema!(sp::Subplot, xmin, xmax, ymin, ymax) expand_extrema!(sp[:yaxis], (ymin, ymax)) end -Commons.get_size(sp::Subplot) = Commons.get_size(sp.plt) -Commons.get_thickness_scaling(sp::Subplot) = Commons.get_thickness_scaling(sp.plt) -end # Subplots +Commons.get_size(sp::Subplot) = get_size(sp.plt) +Commons.get_thickness_scaling(sp::Subplot) = get_thickness_scaling(sp.plt) + +end # module + +# ------------------------------------------------------------------- + +using .Subplots + +Commons.leftpad(sp::Subplot) = sp.minpad[1] +Commons.toppad(sp::Subplot) = sp.minpad[2] +Commons.rightpad(sp::Subplot) = sp.minpad[3] +Commons.bottompad(sp::Subplot) = sp.minpad[4] + +function attr!(sp::Subplot; kw...) + plotattributes = KW(kw) + PlotsBase.Commons.preprocess_attributes!(plotattributes) + for (k, v) in plotattributes + if haskey(_subplot_defaults, k) + sp[k] = v + else + @warn "unused key $k in subplot attr" + end + end + sp +end diff --git a/PlotsBase/src/Surfaces.jl b/PlotsBase/src/Surfaces.jl index 90c2c7794..12eb688be 100644 --- a/PlotsBase/src/Surfaces.jl +++ b/PlotsBase/src/Surfaces.jl @@ -2,10 +2,11 @@ module Surfaces export SurfaceFunction, Surface -import PlotsBase: PlotsBase, expand_extrema!, Commons -using PlotsBase.Axes: Axis -using RecipesPipeline: AbstractSurface, Surface -using PlotsBase.Commons +import PlotsBase: PlotsBase, expand_extrema! +using ..RecipesPipeline: AbstractSurface, Surface + +using ..Commons +using ..Axes function PlotsBase.expand_extrema!(a::Axis, surf::Surface) ex = a[:extrema] @@ -19,4 +20,9 @@ struct SurfaceFunction <: AbstractSurface end Commons.handle_surface(z::Surface) = permutedims(z.surf) -end + +end # module + +# ------------------------------------------------------------------- + +using .Surfaces diff --git a/PlotsBase/src/Ticks.jl b/PlotsBase/src/Ticks.jl index af37828f5..e5c9f2b9d 100644 --- a/PlotsBase/src/Ticks.jl +++ b/PlotsBase/src/Ticks.jl @@ -1,24 +1,31 @@ module Ticks -export get_ticks, _has_ticks, _transform_ticks, get_minor_ticks, no_minor_intervals -using PlotsBase.Commons -using PlotsBase.Dates +export _has_ticks, _transform_ticks, get_minor_ticks +export no_minor_intervals, num_minor_intervals, ticks_type + +using ..Commons +using ..Dates const DEFAULT_MINOR_INTERVALS = Ref(5) # 5 intervals -> 4 ticks +ticks_type(ticks::AVec{<:Real}) = :ticks +ticks_type(ticks::AVec{<:AbstractString}) = :labels +ticks_type(ticks::Tuple{<:Union{AVec,Tuple},<:Union{AVec,Tuple}}) = :ticks_and_labels +ticks_type(ticks) = :invalid + # get_ticks from axis symbol :x, :y, or :z -get_ticks(ticks::NTuple{2,Any}, args...) = ticks -get_ticks(::Nothing, cvals::T, args...) where {T} = T[], String[] -get_ticks(ticks::Bool, args...) = +Commons.get_ticks(ticks::NTuple{2,Any}, args...) = ticks +Commons.get_ticks(::Nothing, cvals::T, args...) where {T} = T[], String[] +Commons.get_ticks(ticks::Bool, args...) = ticks ? get_ticks(:auto, args...) : get_ticks(nothing, args...) -get_ticks(::T, args...) where {T} = +Commons.get_ticks(::T, args...) where {T} = throw(ArgumentError("Unknown ticks type in get_ticks: $T")) # do not specify array item type to also catch e.g. "xlabel=[]" and "xlabel=([],[])" _has_ticks(v::AVec) = !isempty(v) _has_ticks(t::Tuple{AVec,AVec}) = !isempty(t[1]) -_has_ticks(s::Symbol) = s !== :none +_has_ticks(s::Symbol) = s ≢ :none _has_ticks(b::Bool) = b _has_ticks(::Nothing) = false _has_ticks(::Any) = true @@ -44,11 +51,11 @@ function num_minor_intervals(axis) end no_minor_intervals(axis) = - if (n_intervals = axis[:minorticks]) === false + if (n_intervals = axis[:minorticks]) ≡ false true # must be tested with `===` since Bool <: Integer elseif n_intervals ∈ (:none, nothing) true - elseif (n_intervals === :auto && !axis[:minorgrid]) + elseif (n_intervals ≡ :auto && !axis[:minorgrid]) true else false @@ -97,4 +104,8 @@ function get_minor_ticks(sp, axis, ticks_and_labels) minorticks[amin .≤ minorticks .≤ amax] end -end # Ticks +end # module + +# ------------------------------------------------------------------- + +using .Ticks diff --git a/PlotsBase/src/abstract_backend.jl b/PlotsBase/src/abstract_backend.jl deleted file mode 100644 index ba6530f33..000000000 --- a/PlotsBase/src/abstract_backend.jl +++ /dev/null @@ -1,205 +0,0 @@ -const _plots_project = Pkg.Types.read_package(normpath(@__DIR__, "..", "Project.toml")) -const _current_plots_version = _plots_project.version -const _plots_compats = _plots_project.compat - -const _backendSymbol = Dict{DataType,Symbol}(NoBackend => :none) -const _backendType = Dict{Symbol,DataType}(:none => NoBackend) -const _backend_packages = (gaston = :Gaston, gr = :GR, unicodeplots = :UnicodePlots, pgfplotsx = :PGFPlotsX, pythonplot = :PythonPlot, plotly = :Plotly, plotlyjs = :PlotlyJS, hdf5 = :HDF5) -const _initialized_backends = Set{Symbol}() -const _backends = keys(_backend_packages) - -const _plots_deps = let toml = Pkg.TOML.parsefile(normpath(@__DIR__, "..", "Project.toml")) - merge(toml["deps"], toml["extras"]) -end - -function _check_installed(backend::Union{Module,AbstractString,Symbol}; warn = true) - sym = Symbol(lowercase(string(backend))) - if warn && !haskey(_backend_packages, sym) - @warn "backend `$sym` is not compatible with `Plots`." - return - end - # lowercase -> CamelCase, falling back to the given input for `PlotlyBase` ... - str = string(get(_backend_packages, sym, backend)) - str == "Plotly" && (str *= "Base") # FIXME: `Plots` inconsistency, `plotly` should be named `plotlybase` - # check supported - if warn && !haskey(_plots_compats, str) - @warn "backend `$str` is not compatible with `Plots`." - return - end - # check installed - pkg_id = Base.identify_package(str) - version = if pkg_id === nothing - nothing - else - get(Pkg.dependencies(), pkg_id.uuid, (; version = nothing)).version - end - version === nothing && @warn "backend `$str` is not installed." - version -end - -_create_backend_figure(plt::Plot) = nothing -_initialize_subplot(plt::Plot, sp::Subplot) = nothing - -_series_added(plt::Plot, series::Series) = nothing -_series_updated(plt::Plot, series::Series) = nothing - -_before_layout_calcs(plt::Plot) = nothing - -title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefontsize] * pt -guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefontsize] * pt - -closeall(::AbstractBackend) = nothing - -mutable struct CurrentBackend - sym::Symbol - pkg::AbstractBackend -end - -CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym)) - -""" -Returns the current plotting package name. Initializes package on first call. -""" -backend() = CURRENT_BACKEND.pkg - -"Returns a list of supported backends" -backends() = _backends - -backend_name() = CURRENT_BACKEND.sym -_backend_instance(sym::Symbol)::AbstractBackend = _backendType[sym]() - -backend_package_name(sym::Symbol = backend_name()) = get(_backend_packages, sym, :None) - -# Traits to be implemented by the extensions -backend_name(::AbstractBackend) = @info "`backend_name(::Backend) not implemented." -backend_package_name(::AbstractBackend) = - @info "`backend_package_name(::Backend) not implemented." - -initialized(sym::Symbol) = sym ∈ _initialized_backends - -""" -Set the plot backend. -""" -function backend(pkg::AbstractBackend) - sym = backend_name(pkg) - if !initialized(sym) - _initialize_backend(pkg) - push!(_initialized_backends, sym) - end - CURRENT_BACKEND.sym = sym - CURRENT_BACKEND.pkg = pkg - pkg -end - -backend(sym::Symbol) = - if sym in _backends - if initialized(sym) - backend(_backend_instance(sym)) - else - name = backend_package_name(sym) - @warn "`:$sym` is not initialized, import it first to trigger the extension --- e.g. $(name === nothing ? '`' : string("`import ", name, ";")) $sym()`." - backend() - end - else - @error "Unsupported backend $sym" - end - -function get_backend_module(name::Symbol) - ext = Base.get_extension(@__MODULE__, Symbol(name, "Ext")) - if !isnothing(ext) - return ext, ext.get_concrete_backend() - else - @error "Extension $name is not loaded yet, run `import $name` to load it" - return nothing - end -end - -# -- Create backend init functions by hand as the corresponding structs do not -# exist yet - -for be in _backends - @eval begin - function $be(; kw...) - default(; reset = false, kw...) - backend(Symbol($be)) - end - export $be - end -end - -# --------------------------------------------------------- -# create the various `is_xxx_supported` and `supported_xxxs` methods -# these methods should be overloaded (dispatched) by each backend in its init_code -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - @eval begin - $f1(::AbstractBackend, $s) = false - $f1(be::AbstractBackend, $s::AbstractVector) = all(v -> $f1(be, v), $s) - $f1($s) = $f1(backend(), $s) - $f2() = $f2(backend()) - end -end -# ----------------------------------------------------------------------------- - -should_warn_on_unsupported(::AbstractBackend) = _plot_defaults[:warn_on_unsupported] - -const _already_warned = Dict{Symbol,Set{Symbol}}() -function warn_on_unsupported_attrs(pkg::AbstractBackend, plotattributes) - _to_warn = Set{Symbol}() - bend = backend_name(pkg) - already_warned = get!(_already_warned, bend) do - Set{Symbol}() - end - extra_kwargs = Dict{Symbol,Any}() - for k in PlotsBase.explicitkeys(plotattributes) - (is_attr_supported(pkg, k) && k ∉ keys(Commons._deprecated_attributes)) && continue - k in Commons._suppress_warnings && continue - if ismissing(default(k)) - extra_kwargs[k] = pop_kw!(plotattributes, k) - elseif plotattributes[k] != default(k) - k in already_warned || push!(_to_warn, k) - end - end - - if !isempty(_to_warn) && - get(plotattributes, :warn_on_unsupported, should_warn_on_unsupported(pkg)) - for k in sort(collect(_to_warn)) - push!(already_warned, k) - if k in keys(Commons._deprecated_attributes) - @warn """ - Keyword argument `$k` is deprecated. - Please use `$(Commons._deprecated_attributes[k])` instead. - """ - else - @warn "Keyword argument $k not supported with $pkg. Choose from: $(join(supported_attrs(pkg), ", "))" - end - end - end - extra_kwargs -end - -function warn_on_unsupported(pkg::AbstractBackend, plotattributes) - get(plotattributes, :warn_on_unsupported, should_warn_on_unsupported(pkg)) || return - is_seriestype_supported(pkg, plotattributes[:seriestype]) || - @warn "seriestype $(plotattributes[:seriestype]) is unsupported with $pkg. Choose from: $(supported_seriestypes(pkg))" - is_style_supported(pkg, plotattributes[:linestyle]) || - @warn "linestyle $(plotattributes[:linestyle]) is unsupported with $pkg. Choose from: $(supported_styles(pkg))" - is_marker_supported(pkg, plotattributes[:markershape]) || - @warn "markershape $(plotattributes[:markershape]) is unsupported with $pkg. Choose from: $(supported_markers(pkg))" -end - -function warn_on_unsupported_scales(pkg::AbstractBackend, plotattributes::AKW) - get(plotattributes, :warn_on_unsupported, should_warn_on_unsupported(pkg)) || return - for k in (:xscale, :yscale, :zscale, :scale) - if haskey(plotattributes, k) - v = plotattributes[k] - if !all(is_scale_supported.(Ref(pkg), v)) - @warn """ - scale $v is unsupported with $pkg. - Choose from: $(supported_scales(pkg)) - """ - end - end - end -end diff --git a/PlotsBase/src/alignment.jl b/PlotsBase/src/alignment.jl index 1588a6dcd..0969a5837 100644 --- a/PlotsBase/src/alignment.jl +++ b/PlotsBase/src/alignment.jl @@ -17,7 +17,7 @@ text_size(lab::PlotText, sz::Number, rot::Number = 0) = text_size(length(lab.str # account for the size/length/rotation of tick labels function tick_padding(sp::Subplot, axis::Axis) - if (ticks = get_ticks(sp, axis)) === nothing + if (ticks = get_ticks(sp, axis)) ≡ nothing 0mm else vals, labs = ticks @@ -26,7 +26,7 @@ function tick_padding(sp::Subplot, axis::Axis) longest_label = maximum(length(lab) for lab in labs) # generalize by "rotating" y labels - rot = axis[:rotation] + (axis[:letter] === :y ? 90 : 0) + rot = axis[:rotation] + (axis[:letter] ≡ :y ? 90 : 0) # # we need to compute the size of the ticks generically # # this means computing the bounding box and then getting the width/height diff --git a/PlotsBase/src/animation.jl b/PlotsBase/src/animation.jl index 434c747b1..859caf35b 100644 --- a/PlotsBase/src/animation.jl +++ b/PlotsBase/src/animation.jl @@ -235,9 +235,9 @@ function _animate(forloop::Expr, args...; type::Symbol = :none) push!(block.args, :($countersym += 1)) # add a final call to `gif(anim)`? - retval = if type === :gif + retval = if type ≡ :gif :(PlotsBase.gif($animsym; $(animationsKwargs...))) - elseif type === :apng + elseif type ≡ :apng :(PlotsBase.apng($animsym; $(animationsKwargs...))) else animsym diff --git a/PlotsBase/src/axes_utils.jl b/PlotsBase/src/axes_utils.jl index 46e0a461a..735ce0c26 100644 --- a/PlotsBase/src/axes_utils.jl +++ b/PlotsBase/src/axes_utils.jl @@ -1,6 +1,6 @@ const _label_func = Dict{Symbol,Function}(:log10 => x -> "10^$x", :log2 => x -> "2^$x", :ln => x -> "e^$x") -labelfunc(scale::Symbol, backend::AbstractBackend) = get(_label_func, scale, string) +labelfunc(scale::Symbol, ::AbstractBackend) = get(_label_func, scale, string) const _label_func_tex = Dict{Symbol,Function}( :log10 => x -> "10^{$x}", @@ -23,7 +23,7 @@ function optimal_ticks_and_labels(ticks, alims, scale, formatter) # rather than on the input format # TODO: maybe: non-trivial scale (:ln, :log2, :log10) for date/datetime - if ticks === nothing && noop + if ticks ≡ nothing && noop if formatter == RecipesPipeline.dateformatter # optimize_datetime_ticks returns ticks and labels(!) based on # integers/floats corresponding to the DateTime type. Thus, the axes @@ -41,7 +41,7 @@ function optimal_ticks_and_labels(ticks, alims, scale, formatter) end # get a list of well-laid-out ticks - scaled_ticks = if ticks === nothing + scaled_ticks = if ticks ≡ nothing optimize_ticks( sf(amin), sf(amax); @@ -75,12 +75,12 @@ function optimal_ticks_and_labels(ticks, alims, scale, formatter) unscaled_ticks, labels end -Ticks.get_ticks(ticks::Symbol, cvals::T, dvals, args...) where {T} = - if ticks === :none +Commons.get_ticks(ticks::Symbol, cvals::T, dvals, args...) where {T} = + if ticks ≡ :none T[], String[] elseif !isempty(dvals) n = length(dvals) - if ticks === :all || n < 16 + if ticks ≡ :all || n < 16 cvals, string.(dvals) else Δ = ceil(Int, n / 10) @@ -91,9 +91,9 @@ Ticks.get_ticks(ticks::Symbol, cvals::T, dvals, args...) where {T} = optimal_ticks_and_labels(nothing, args...) end -Ticks.get_ticks(ticks::AVec, cvals, dvals, args...) = +Commons.get_ticks(ticks::AVec, cvals, dvals, args...) = optimal_ticks_and_labels(ticks, args...) -Ticks.get_ticks(ticks::Int, dvals, cvals, args...) = +Commons.get_ticks(ticks::Int, dvals, cvals, args...) = if isempty(dvals) optimal_ticks_and_labels(ticks, args...) else @@ -101,18 +101,18 @@ Ticks.get_ticks(ticks::Int, dvals, cvals, args...) = cvals[rng], string.(dvals[rng]) end -function get_labels(formatter::Symbol, scaled_ticks, scale) +get_labels(formatter::Symbol, scaled_ticks, scale) = if formatter in (:auto, :plain, :scientific, :engineering) - return map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, formatter)) - elseif formatter === :latex - return map( + map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, formatter)) + elseif formatter ≡ :latex + map( l -> string("\$", replace(convert_sci_unicode(l), '×' => "\\times"), "\$"), get_labels(:auto, scaled_ticks, scale), ) - elseif formatter === :none - return String[] + elseif formatter ≡ :none + String[] end -end + function get_labels(formatter::Function, scaled_ticks, scale) sf, invsf, _ = scale_inverse_scale_func(scale) fticks = map(formatter ∘ invsf, scaled_ticks) @@ -120,7 +120,7 @@ function get_labels(formatter::Function, scaled_ticks, scale) # CategoricalArrays's recipe gives "missing" label to those filter!(!ismissing, fticks) eltype(fticks) <: Number && return get_labels(:auto, map(sf, fticks), scale) - return fticks + fticks end # Ticks getter functions @@ -190,7 +190,7 @@ end # whenever we have discrete values, we automatically set the ticks to match. # we return (continuous_value, discrete_index) discrete_value!(plotattributes, letter::Symbol, dv) = - let l = if plotattributes[:permute] !== :none + let l = if plotattributes[:permute] ≢ :none filter(!=(letter), plotattributes[:permute]) |> only else letter @@ -256,21 +256,21 @@ function add_major_or_minor_segments_2d( factor, cond, ) - ticks === nothing && return + ticks ≡ nothing && return if cond f, invf = scale_inverse_scale_func(oax[:scale]) - tick_start, tick_stop = if sp[:framestyle] === :origin + tick_start, tick_stop = if sp[:framestyle] ≡ :origin oamin, oamax = oamM t = invf(f(0) + factor * (f(oamax) - f(oamin))) (-t, t) else - ticks_in = ax[:tick_direction] === :out ? -1 : 1 + ticks_in = ax[:tick_direction] ≡ :out ? -1 : 1 oa1, oa2 = oas t = invf(f(oa1) + factor * (f(oa2) - f(oa1)) * ticks_in) (oa1, t) end end - isy = ax[:letter] === :y + isy = ax[:letter] ≡ :y for tick in ticks (ax[:showaxis] && cond) && push!( tick_segments, @@ -299,23 +299,23 @@ function axis_drawing_info(sp, letter) segments, tick_segments, grid_segments, minorgrid_segments, border_segments = map(_ -> Segments(2), 1:5) - if sp[:framestyle] !== :none - isy = letter === :y + if sp[:framestyle] ≢ :none + isy = letter ≡ :y oa1, oa2 = oas = if sp[:framestyle] in (:origin, :zerolines) 0, 0 else xor(ax[:mirror], oax[:flip]) ? reverse(oamM) : oamM end if ax[:showaxis] - if sp[:framestyle] !== :grid + if sp[:framestyle] ≢ :grid push!(segments, reverse_if((amin, oa1), isy), reverse_if((amax, oa1), isy)) # don't show the 0 tick label for the origin framestyle if ( - sp[:framestyle] === :origin && + sp[:framestyle] ≡ :origin && ticks ∉ (:none, nothing, false) && length(ticks) > 1 ) - if (i = findfirst(==(0), ticks[1])) !== nothing + if (i = findfirst(==(0), ticks[1])) ≢ nothing deleteat!(ticks[1], i) deleteat!(ticks[2], i) end @@ -329,7 +329,7 @@ function axis_drawing_info(sp, letter) ) end if ax[:ticks] ∉ (:none, nothing, false) - ax_length = letter === :x ? height(sp.plotarea).value : width(sp.plotarea).value + ax_length = letter ≡ :x ? height(sp.plotarea).value : width(sp.plotarea).value # add major grid segments add_major_or_minor_segments_2d( @@ -343,9 +343,9 @@ function axis_drawing_info(sp, letter) tick_segments, grid_segments, grid_factor_2d[] / ax_length, - ax[:tick_direction] !== :none, + ax[:tick_direction] ≢ :none, ) - if sp[:framestyle] === :box + if sp[:framestyle] ≡ :box add_major_or_minor_segments_2d( sp, ax, @@ -357,7 +357,7 @@ function axis_drawing_info(sp, letter) tick_segments, grid_segments, grid_factor_2d[] / ax_length, - ax[:tick_direction] !== :none, + ax[:tick_direction] ≢ :none, ) end @@ -376,7 +376,7 @@ function axis_drawing_info(sp, letter) grid_factor_2d[] / 2ax_length, true, ) - if sp[:framestyle] === :box + if sp[:framestyle] ≡ :box add_major_or_minor_segments_2d( sp, ax, @@ -419,16 +419,16 @@ function add_major_or_minor_segments_3d( factor, cond, ) - ticks === nothing && return + ticks ≡ nothing && return if cond f, invf = scale_inverse_scale_func(nax[:scale]) - tick_start, tick_stop = if sp[:framestyle] === :origin + tick_start, tick_stop = if sp[:framestyle] ≡ :origin namin, namax = namM t = invf(f(0) + factor * (f(namax) - f(namin))) (-t, t) else na0, na1 = nas - ticks_in = ax[:tick_direction] === :out ? -1 : 1 + ticks_in = ax[:tick_direction] ≡ :out ? -1 : 1 t = invf(f(na0) + factor * (f(na1) - f(na0)) * ticks_in) (na0, t) end @@ -467,12 +467,12 @@ function axis_drawing_info_3d(sp, letter) segments, tick_segments, grid_segments, minorgrid_segments, border_segments = map(_ -> Segments(3), 1:5) - if sp[:framestyle] !== :none # && letter === :x + if sp[:framestyle] ≢ :none # && letter ≡ :x na0, na1 = nas = if sp[:framestyle] in (:origin, :zerolines) 0, 0 else - reverse_if(reverse_if(namM, letter === :y), xor(ax[:mirror], nax[:flip])) + reverse_if(reverse_if(namM, letter ≡ :y), xor(ax[:mirror], nax[:flip])) end fa0, fa1 = fas = if sp[:framestyle] in (:origin, :zerolines) 0, 0 @@ -480,7 +480,7 @@ function axis_drawing_info_3d(sp, letter) reverse_if(famM, xor(ax[:mirror], fax[:flip])) end if ax[:showaxis] - if sp[:framestyle] !== :grid + if sp[:framestyle] ≢ :grid push!( segments, sort_3d_axes(amin, na0, fa0, letter), @@ -488,11 +488,11 @@ function axis_drawing_info_3d(sp, letter) ) # don't show the 0 tick label for the origin framestyle if ( - sp[:framestyle] === :origin && + sp[:framestyle] ≡ :origin && ticks ∉ (:none, nothing, false) && length(ticks) > 1 ) - if (i = findfirst(==(0), ticks[1])) !== nothing + if (i = findfirst(==(0), ticks[1])) ≢ nothing deleteat!(ticks[1], i) deleteat!(ticks[2], i) end @@ -519,7 +519,7 @@ function axis_drawing_info_3d(sp, letter) tick_segments, grid_segments, grid_factor_3d[], - ax[:tick_direction] !== :none, + ax[:tick_direction] ≢ :none, ) # add minor grid segments diff --git a/PlotsBase/src/backends.jl b/PlotsBase/src/backends.jl new file mode 100644 index 000000000..c1817a502 --- /dev/null +++ b/PlotsBase/src/backends.jl @@ -0,0 +1,259 @@ +const _default_supported_syms = :attr, :seriestype, :marker, :style, :scale + +_f1_sym(sym::Symbol) = Symbol("is_$(sym)_supported") +_f2_sym(sym::Symbol) = Symbol("supported_$(sym)s") + +struct NoBackend <: AbstractBackend end + +backend_name(::NoBackend) = :none +should_warn_on_unsupported(::NoBackend) = false + +for sym in _default_supported_syms + @eval begin + $(_f1_sym(sym))(::NoBackend, $sym::Symbol) = true + $(_f2_sym(sym))(::NoBackend) = Commons.$(Symbol("_all_$(sym)s")) + end +end + +_display(::Plot{NoBackend}) = + @warn "No backend activated yet. Load the backend library and call the activation function to do so.\nE.g. `import GR; gr()` activates the GR backend." + +const _backendSymbol = Dict{DataType,Symbol}(NoBackend => :none) +const _backendType = Dict{Symbol,DataType}(:none => NoBackend) +const _backend_packages = (unicodeplots = :UnicodePlots, pythonplot = :PythonPlot, pgfplotsx = :PGFPlotsX, plotlyjs = :PlotlyJS, gaston = :Gaston, plotly = nothing, none = nothing, hdf5 = :HDF5, gr = :GR) +const _supported_backends = keys(_backend_packages) +const _initialized_backends = Set([:none]) + +function _check_installed(pkg::Union{Module,AbstractString,Symbol}; warn = true) + name = Symbol(lowercase(string(pkg))) + if warn && !haskey(_backend_packages, name) + @warn "backend `$name` is not compatible with `PlotsBase`." + return + end + # lowercase -> CamelCase, falling back to the given input for `PlotlyBase` ... + pkg_str = string(get(_backend_packages, name, pkg)) + pkg_str == "Plotly" && (pkg_str *= "Base") # FIXME: `PlotsBase` inconsistency, `plotly` should be named `plotlybase` + # check supported + if warn && !haskey(_compat, pkg_str) + @warn "package `$pkg_str` is not compatible with `PlotsBase`." + return + end + # check installed + version = if (pkg_id = Base.identify_package(pkg_str)) ≡ nothing + nothing + else + get(Pkg.dependencies(), pkg_id.uuid, (; version = nothing)).version + end + version ≡ nothing && @warn "`package $pkg_str` is not installed." + version +end + +_create_backend_figure(plt::Plot) = nothing +_initialize_subplot(plt::Plot, sp::Subplot) = nothing + +_series_added(plt::Plot, series::Series) = nothing +_series_updated(plt::Plot, series::Series) = nothing + +_before_layout_calcs(plt::Plot) = nothing + +title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefontsize] * pt +guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefontsize] * pt + +closeall(::AbstractBackend) = nothing + +mutable struct CurrentBackend + name::Symbol + instance::AbstractBackend +end + +@inline backend_type(name::Symbol) = _backendType[name] +@inline backend_instance(name::Symbol) = backend_type(name)() +@inline backend(type::Type{<:AbstractBackend}) = backend(type()) + +CurrentBackend(name::Symbol) = CurrentBackend(name, backend_instance(name)) + +const CURRENT_BACKEND = CurrentBackend(:none) + +"returns the current plotting package backend. Initializes package on first call." +@inline backend() = CURRENT_BACKEND.instance + +"returns a list of supported backends." +@inline backends() = _supported_backends + +@inline backend_name() = CURRENT_BACKEND.name +@inline backend_package_name(name::Symbol = backend_name()) = + get(_backend_packages, name, nothing) + +# Traits to be implemented by the extensions +backend_name(::AbstractBackend) = @info "`backend_name(::Backend) not implemented." +backend_package_name(::AbstractBackend) = + @info "`backend_package_name(::Backend) not implemented." + +"set the plot backend." +function backend(instance::AbstractBackend) + name = backend_name(instance) + if name ∈ _supported_backends + CURRENT_BACKEND.name = name + CURRENT_BACKEND.instance = instance + else + @error "Unsupported backend $name" + end + instance +end + +backend(name::Symbol) = + if name ∈ _supported_backends + if name ∈ _initialized_backends + backend(backend_type(name)) + else + pkg_name = backend_package_name(name) + @warn "`:$name` is not initialized, import it first to trigger the extension --- e.g. `$(pkg_name ≡ nothing ? "" : "import $pkg_name; ")$name()`." + backend() + end + else + @error "Unsupported backend $name" + end + +function get_backend_module(pkg_name::Symbol) + ext = Base.get_extension(@__MODULE__, Symbol("$(pkg_name)Ext")) + concrete_backend = if ext ≡ nothing + @error "Extension $pkg_name is not loaded yet, run `import $pkg_name` to load it" + nothing + else + ext.get_concrete_backend() + end + ext, concrete_backend +end + +# create backend init functions by hand as the corresponding structs do not exist yet +for be in _supported_backends + @eval begin + function $be(; kw...) + default(; reset = false, kw...) + backend(Symbol($be)) + end + export $be + end +end + +# create the various `is_xxx_supported` and `supported_xxxs` methods +# these methods should be overloaded (dispatched) by each backend in its init_code +for sym in _default_supported_syms + f1 = _f1_sym(sym) + f2 = _f2_sym(sym) + @eval begin + $f1(::AbstractBackend, $sym) = false + $f1(be::AbstractBackend, $sym::AbstractVector) = all(v -> $f1(be, v), $sym) + $f1($sym) = $f1(backend(), $sym) + $f2() = $f2(backend()) + end +end + +function backend_defines(be_type::Symbol, be::Symbol) + be_sym = QuoteNode(be) + blk = Expr( + :block, + :(get_concrete_backend() = $be_type), + :(PlotsBase.backend_name(::$be_type)::Symbol = $be_sym), + :( + PlotsBase.backend_package_name(::$be_type)::Symbol = + PlotsBase.backend_package_name($be_sym) + ), + ) + #= + Overload (dispatch) abstract `is_xxx_supported` and `supported_xxxs` methods, + results in: + PlotsBase.is_attr_supported(::GRbackend, attrname) -> Bool + ... + PlotsBase.supported_attrs(::GRbackend) -> ::Vector{Symbol} + ... + PlotsBase.supported_scales(::GRbackend) -> ::Vector{Symbol} + =# + for sym in _default_supported_syms + be_syms = Symbol("_$(be)_$(sym)s") + push!( + blk.args, + :(PlotsBase.$(_f1_sym(sym))(::$be_type, $sym::Symbol)::Bool = $sym in $be_syms), + :(PlotsBase.$(_f2_sym(sym))(::$be_type)::Vector = sort!(collect($be_syms))), + ) + end + blk +end + +"extra init step for an extension" +extension_init(::AbstractBackend) = nothing + +"generate extension `__init__` function, and common defines" +macro extension_static(be_type, be) + be_sym = QuoteNode(be) + quote + $(PlotsBase.backend_defines(be_type, be)) + function __init__() + PlotsBase._backendType[$be_sym] = $be_type + PlotsBase._backendSymbol[$be_type] = $be_sym + push!(PlotsBase._initialized_backends, $be_sym) + ccall(:jl_generating_output, Cint, ()) == 1 && return + PlotsBase.extension_init($be_type()) + @debug "Initialized $be_type backend in PlotsBase; run `$be()` to activate it." + end + end |> esc +end + +should_warn_on_unsupported(::AbstractBackend) = _plot_defaults[:warn_on_unsupported] + +const _already_warned = Dict{Symbol,Set{Symbol}}() +function warn_on_unsupported_attrs(pkg::AbstractBackend, plotattributes) + _to_warn = Set{Symbol}() + bend = backend_name(pkg) + already_warned = get!(() -> Set{Symbol}(), _already_warned, bend) + extra_kwargs = Dict{Symbol,Any}() + for k in PlotsBase.explicitkeys(plotattributes) + (is_attr_supported(pkg, k) && k ∉ keys(Commons._deprecated_attributes)) && continue + k in Commons._suppress_warnings && continue + if ismissing(default(k)) + extra_kwargs[k] = pop_kw!(plotattributes, k) + elseif plotattributes[k] != default(k) + k in already_warned || push!(_to_warn, k) + end + end + + if !isempty(_to_warn) && + get(plotattributes, :warn_on_unsupported, should_warn_on_unsupported(pkg)) + for k in sort!(collect(_to_warn)) + push!(already_warned, k) + if k in keys(Commons._deprecated_attributes) + @warn """ + Keyword argument `$k` is deprecated. + Please use `$(Commons._deprecated_attributes[k])` instead. + """ + else + @warn "Keyword argument $k not supported with $pkg. Choose from: $(join(supported_attrs(pkg), ", "))" + end + end + end + extra_kwargs +end + +function warn_on_unsupported(pkg::AbstractBackend, plotattributes) + get(plotattributes, :warn_on_unsupported, should_warn_on_unsupported(pkg)) || return + is_seriestype_supported(pkg, plotattributes[:seriestype]) || + @warn "seriestype $(plotattributes[:seriestype]) is unsupported with $pkg. Choose from: $(supported_seriestypes(pkg))" + is_style_supported(pkg, plotattributes[:linestyle]) || + @warn "linestyle $(plotattributes[:linestyle]) is unsupported with $pkg. Choose from: $(supported_styles(pkg))" + is_marker_supported(pkg, plotattributes[:markershape]) || + @warn "markershape $(plotattributes[:markershape]) is unsupported with $pkg. Choose from: $(supported_markers(pkg))" +end + +function warn_on_unsupported_scales(pkg::AbstractBackend, plotattributes::AKW) + get(plotattributes, :warn_on_unsupported, should_warn_on_unsupported(pkg)) || return + for k in (:xscale, :yscale, :zscale, :scale) + haskey(plotattributes, k) || continue + v = plotattributes[k] + if !all(is_scale_supported.(Ref(pkg), v)) + @warn """ + scale $v is unsupported with $pkg. + Choose from: $(supported_scales(pkg)) + """ + end + end +end diff --git a/PlotsBase/src/backends/nobackend.jl b/PlotsBase/src/backends/nobackend.jl deleted file mode 100644 index 0135b9c1b..000000000 --- a/PlotsBase/src/backends/nobackend.jl +++ /dev/null @@ -1,15 +0,0 @@ -struct NoBackend <: AbstractBackend end - -backend_name(::NoBackend) = :none - -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - @eval begin - $f1(::NoBackend, $s::Symbol) = true - $f2(::NoBackend) = $(getproperty(Commons, Symbol("_all_", s, 's'))) - end -end - -_display(::Plot{NoBackend}) = - @info "No backend activated yet. Load the backend library and call the activation function to do so.\nE.g. `import GR; gr()` activates the GR backend." diff --git a/PlotsBase/src/examples.jl b/PlotsBase/src/examples.jl index 96c40facc..f120e2360 100644 --- a/PlotsBase/src/examples.jl +++ b/PlotsBase/src/examples.jl @@ -460,7 +460,7 @@ const _examples = PlotExample[ ), PlotExample( # 29 "Layouts, margins, label rotation, title location", - :(using PlotsBase.PlotMeasures), # for Measures, e.g. mm and px + :(using PlotsBase.Commons), # for Measures, e.g. mm and px quote plot( rand(100, 6), @@ -964,9 +964,9 @@ const _examples = PlotExample[ wireframe( args..., title = "wire-flip-$ax", - xflip = ax === :x, - yflip = ax === :y, - zflip = ax === :z; + xflip = ax ≡ :x, + yflip = ax ≡ :y, + zflip = ax ≡ :z; kw..., ), ) @@ -978,9 +978,9 @@ const _examples = PlotExample[ wireframe( args..., title = "wire-mirror-$ax", - xmirror = ax === :x, - ymirror = ax === :y, - zmirror = ax === :z; + xmirror = ax ≡ :x, + ymirror = ax ≡ :y, + zmirror = ax ≡ :z; kw..., ), ) @@ -1254,9 +1254,11 @@ const _examples = PlotExample[ ] # Some constants for PlotDocs and PlotReferenceImages -_animation_examples = [2, 31] +_animation_examples = [02, 31] _backend_skips = Dict( - :gr => [25, 30], # TODO: add back when StatsPlots is available + :none => Int[], + :pythonplot => Int[], + :gr => [25, 30], # TODO: add back when StatsPlots is available :plotlyjs => [ 21, 24, @@ -1274,7 +1276,7 @@ _backend_skips = Dict( 66, # bar: vector-valued `color` unsupported ], :pgfplotsx => [ - 6, # images + 06, # images 16, # pgfplots thinks the upper panel is too small 32, # spy 49, # polar heatmap @@ -1283,8 +1285,8 @@ _backend_skips = Dict( 62, # fillstyle unsupported ], :unicodeplots => [ - 5, # limits issue - 6, # embedded images supported, but requires `using ImageInTerminal`, disable for docs + 05, # limits issue + 06, # embedded images supported, but requires `using ImageInTerminal`, disable for docs 16, # nested layout unsupported 21, # custom markers unsupported 26, # nested layout unsupported @@ -1313,8 +1315,6 @@ _backend_skips = Dict( ) _backend_skips[:plotly] = _backend_skips[:plotlyjs] -_backend_skips[:pythonplot] = Int[] - # --------------------------------------------------------------------------------- # replace `f(args...)` with `f(rng, args...)` for `f ∈ (rand, randn)` replace_rand(ex) = ex @@ -1371,16 +1371,16 @@ function test_examples( PlotsBase.Commons.debug!($debug) backend($(QuoteNode(pkgname))) rng = $rng - rng === nothing || Random.seed!(rng, PlotsBase.PLOTS_SEED) + rng ≡ nothing || Random.seed!(rng, PlotsBase.PLOTS_SEED) theme(:default) end) - (imp = _examples[i].imports) === nothing || Base.eval(m, imp) + (imp = _examples[i].imports) ≡ nothing || Base.eval(m, imp) exprs = _examples[i].exprs - rng === nothing || (exprs = PlotsBase.replace_rand(exprs)) + rng ≡ nothing || (exprs = PlotsBase.replace_rand(exprs)) Base.eval(m, exprs) disp && Base.eval(m, :(gui(current()))) - callback === nothing || callback(m, pkgname, i) + callback ≡ nothing || callback(m, pkgname, i) m.PlotsBase.current() end @@ -1415,7 +1415,7 @@ function test_examples( end # COV_EXCL_STOP end - sleep === nothing || Base.sleep(sleep) + sleep ≡ nothing || Base.sleep(sleep) end plts end diff --git a/PlotsBase/src/layouts.jl b/PlotsBase/src/layouts.jl index 2f80680f0..b43ad09c3 100644 --- a/PlotsBase/src/layouts.jl +++ b/PlotsBase/src/layouts.jl @@ -1,123 +1,12 @@ -# NOTE: (0,0) is the top-left !!! - -to_pixels(m::AbsoluteLength) = m.value / 0.254 - -origin(bbox::BoundingBox) = left(bbox) + width(bbox) / 2, top(bbox) + height(bbox) / 2 -left(bbox::BoundingBox) = bbox.x0[1] -top(bbox::BoundingBox) = bbox.x0[2] -right(bbox::BoundingBox) = left(bbox) + width(bbox) -bottom(bbox::BoundingBox) = top(bbox) + height(bbox) -Base.size(bbox::BoundingBox) = (width(bbox), height(bbox)) - -# Base.:*{T,N}(m1::Length{T,N}, m2::Length{T,N}) = Length{T,N}(m1.value * m2.value) -ispositive(m::Measure) = m.value > 0 - -# union together bounding boxes -function Base.:+(bb1::BoundingBox, bb2::BoundingBox) - # empty boxes don't change the union - ispositive(width(bb1)) || return bb2 - ispositive(height(bb1)) || return bb2 - ispositive(width(bb2)) || return bb1 - ispositive(height(bb2)) || return bb1 - - l = min(left(bb1), left(bb2)) - t = min(top(bb1), top(bb2)) - r = max(right(bb1), right(bb2)) - b = max(bottom(bb1), bottom(bb2)) - BoundingBox(l, t, r - l, b - t) -end - -# convert x,y coordinates from absolute coords to percentages... -# returns x_pct, y_pct -function xy_mm_to_pcts(x::AbsoluteLength, y::AbsoluteLength, figw, figh, flipy = true) - xmm, ymm = x.value, y.value - if flipy - ymm = figh.value - ymm # flip y when origin in bottom-left - end - xmm / figw.value, ymm / figh.value -end - -# convert a bounding box from absolute coords to percentages... -# returns an array of percentages of figure size: [left, bottom, width, height] -function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true) - mms = Float64[f(bb).value for f in (left, bottom, width, height)] - if flipy - mms[2] = figh.value - mms[2] # flip y when origin in bottom-left - end - mms ./ Float64[figw.value, figh.value, figw.value, figh.value] -end - -Base.show(io::IO, bbox::BoundingBox) = print( - io, - "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}", -) - -# ----------------------------------------------------------- -# AbstractLayout - -Base.show(io::IO, layout::AbstractLayout) = print(io, "$(typeof(layout))$(size(layout))") - -make_measure_hor(n::Number) = n * w -make_measure_hor(m::Measure) = m - -make_measure_vert(n::Number) = n * h -make_measure_vert(m::Measure) = m - """ - bbox(x, y, w, h [,originargs...]) - bbox(layout) + grid(args...; kw...) -Create a bounding box for plotting +Create a grid layout for subplots. `args` specify the dimensions, e.g. +`grid(3,2, widths = (0.6,0.4))` creates a grid with three rows and two +columns of different width. """ -function bbox(x, y, w, h, oarg1::Symbol, originargs::Symbol...) - oargs = vcat(oarg1, originargs...) - orighor = :left - origver = :top - for oarg in oargs - if oarg === :center - orighor = origver = oarg - elseif oarg in (:left, :right, :hcenter) - orighor = oarg - elseif oarg in (:top, :bottom, :vcenter) - origver = oarg - else - @warn "Unused origin arg in bbox construction: $oarg" - end - end - bbox(x, y, w, h; h_anchor = orighor, v_anchor = origver) -end - -# create a new bbox -function bbox(x, y, width, height; h_anchor = :left, v_anchor = :top) - x = make_measure_hor(x) - y = make_measure_vert(y) - width = make_measure_hor(width) - height = make_measure_vert(height) - left = if h_anchor === :left - x - elseif h_anchor in (:center, :hcenter) - 0.5w - 0.5width + x - else - 1w - x - width - end - top = if v_anchor === :top - y - elseif v_anchor in (:center, :vcenter) - 0.5h - 0.5height + y - else - 1h - y - height - end - BoundingBox(left, top, width, height) -end - -# this is the available area for drawing everything in this layout... as percentages of total canvas -bbox(layout::AbstractLayout) = layout.bbox -bbox!(layout::AbstractLayout, bb::BoundingBox) = (layout.bbox = bb) - -# layouts are recursive, tree-like structures, and most will have a parent field -Base.parent(layout::AbstractLayout) = layout.parent -parent_bbox(layout::AbstractLayout) = bbox(parent(layout)) +grid(args...; kw...) = GridLayout(args...; kw...) # padding_w(layout::AbstractLayout) = left_padding(layout) + right_padding(layout) # padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout) @@ -130,120 +19,17 @@ update_child_bboxes!( kw..., ) = nothing -left(layout::AbstractLayout) = left(bbox(layout)) -top(layout::AbstractLayout) = top(bbox(layout)) -right(layout::AbstractLayout) = right(bbox(layout)) -bottom(layout::AbstractLayout) = bottom(bbox(layout)) -width(layout::AbstractLayout) = width(bbox(layout)) -height(layout::AbstractLayout) = height(bbox(layout)) - # pass these through to the bbox methods if there's no plotarea -plotarea(layout::AbstractLayout) = bbox(layout) -plotarea!(layout::AbstractLayout, bb::BoundingBox) = bbox!(layout, bb) - -attr(layout::AbstractLayout, k::Symbol) = layout.attr[k] -attr(layout::AbstractLayout, k::Symbol, v) = get(layout.attr, k, v) -attr!(layout::AbstractLayout, v, k::Symbol) = (layout.attr[k] = v) -hasattr(layout::AbstractLayout, k::Symbol) = haskey(layout.attr, k) - -leftpad(layout::AbstractLayout) = 0mm -toppad(layout::AbstractLayout) = 0mm -rightpad(layout::AbstractLayout) = 0mm -bottompad(layout::AbstractLayout) = 0mm - -# ----------------------------------------------------------- -# RootLayout - -# this is the parent of the top-level layout -struct RootLayout <: AbstractLayout end - -Base.show(io::IO, layout::RootLayout) = Base.show_default(io, layout) -Base.parent(::RootLayout) = nothing -parent_bbox(::RootLayout) = DEFAULT_BBOX[] -bbox(::RootLayout) = DEFAULT_BBOX[] - -# ----------------------------------------------------------- -# EmptyLayout - -# contains blank space -mutable struct EmptyLayout <: AbstractLayout - parent::AbstractLayout - bbox::BoundingBox - attr::KW # store label, width, and height for initialization - # label # this is the label that the subplot will take (since we create a layout before initialization) -end -EmptyLayout(parent = RootLayout(); kw...) = EmptyLayout(parent, DEFAULT_BBOX[], KW(kw)) - -Base.size(layout::EmptyLayout) = (0, 0) -Base.length(layout::EmptyLayout) = 0 -Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing +Commons.plotarea(layout::AbstractLayout) = bbox(layout) +Commons.plotarea!(layout::AbstractLayout, bb::BoundingBox) = bbox!(layout, bb) _update_min_padding!(layout::EmptyLayout) = nothing _update_inset_padding!(layout::EmptyLayout) = nothing -# ----------------------------------------------------------- -# GridLayout - -# nested, gridded layout with optional size percentages -mutable struct GridLayout <: AbstractLayout - parent::AbstractLayout - minpad::Tuple # leftpad, toppad, rightpad, bottompad - bbox::BoundingBox - grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion - widths::Vector{Measure} - heights::Vector{Measure} - attr::KW -end - -""" - grid(args...; kw...) - -Create a grid layout for subplots. `args` specify the dimensions, e.g. -`grid(3,2, widths = (0.6,0.4))` creates a grid with three rows and two -columns of different width. -""" -grid(args...; kw...) = GridLayout(args...; kw...) - -function GridLayout( - dims...; - parent = RootLayout(), - widths = zeros(dims[2]), - heights = zeros(dims[1]), - kw..., -) - grid = Matrix{AbstractLayout}(undef, dims...) - layout = GridLayout( - parent, - DEFAULT_MINPAD[], - DEFAULT_BBOX[], - grid, - Measure[w * pct for w in widths], - Measure[h * pct for h in heights], - # convert(Vector{Float64}, widths), - # convert(Vector{Float64}, heights), - KW(kw), - ) - for i in eachindex(grid) - grid[i] = EmptyLayout(layout) - end - layout -end - -Base.size(layout::GridLayout) = size(layout.grid) -Base.length(layout::GridLayout) = length(layout.grid) -Base.getindex(layout::GridLayout, r::Int, c::Int) = layout.grid[r, c] -Base.setindex!(layout::GridLayout, v, r::Int, c::Int) = layout.grid[r, c] = v -Base.setindex!(layout::GridLayout, v, ci::CartesianIndex) = layout.grid[ci] = v - -leftpad(pad) = pad[1] -toppad(pad) = pad[2] -rightpad(pad) = pad[3] -bottompad(pad) = pad[4] - -leftpad(layout::GridLayout) = leftpad(layout.minpad) -toppad(layout::GridLayout) = toppad(layout.minpad) -rightpad(layout::GridLayout) = rightpad(layout.minpad) -bottompad(layout::GridLayout) = bottompad(layout.minpad) +attr(layout::AbstractLayout, k::Symbol) = layout.attr[k] +attr(layout::AbstractLayout, k::Symbol, v) = get(layout.attr, k, v) +attr!(layout::AbstractLayout, v, k::Symbol) = (layout.attr[k] = v) +# hasattr(layout::AbstractLayout, k::Symbol) = haskey(layout.attr, k) # here's how this works... first we recursively "update the minimum padding" (which # means to calculate the minimum size needed from the edge of the subplot to plot area) @@ -491,7 +277,7 @@ end function build_layout(layout::GridLayout, n::Integer, plts::AVec{Plot}) nr, nc = size(layout) subplots = Subplot[] - spmap = PlotsPlots.SubplotMap() + spmap = Plots.SubplotMap() empty = isempty(plts) i = 0 for r in 1:nr, c in 1:nc @@ -512,19 +298,19 @@ function build_layout(layout::GridLayout, n::Integer, plts::AVec{Plot}) merge!(spmap, plt.spmap) inc = length(plt.subplots) end - if get(l.attr, :width, :auto) !== :auto + if get(l.attr, :width, :auto) ≢ :auto layout.widths[c] = attr(l, :width) end - if get(l.attr, :height, :auto) !== :auto + if get(l.attr, :height, :auto) ≢ :auto layout.heights[r] = attr(l, :height) end i += inc elseif isa(l, GridLayout) # sub-grid - if get(l.attr, :width, :auto) !== :auto + if get(l.attr, :width, :auto) ≢ :auto layout.widths[c] = attr(l, :width) end - if get(l.attr, :height, :auto) !== :auto + if get(l.attr, :height, :auto) ≢ :auto layout.heights[r] = attr(l, :height) end l, sps, m = build_layout(l, n - i, plts) @@ -598,7 +384,7 @@ function link_axes!(layout::GridLayout, link::Symbol) link_axes!(layout.grid[r, :], :yaxis) end end - if link === :square + if link ≡ :square if (sps = filter(l -> isa(l, Subplot), layout.grid)) |> !isempty base_axis = sps[1][:xaxis] for sp in sps @@ -607,7 +393,7 @@ function link_axes!(layout::GridLayout, link::Symbol) end end end - if link === :all + if link ≡ :all link_axes!(layout.grid, :xaxis) link_axes!(layout.grid, :yaxis) end @@ -623,7 +409,7 @@ function twin(sp, letter) ax = orig_sp[get_attr_symbol(letter, :axis)] ax[:grid] = false # disable the grid (overlaps with twin axis) end - if orig_sp[:framestyle] === :box + if orig_sp[:framestyle] ≡ :box # incompatible with shared axes (see github.com/JuliaPlots/Plots.jl/issues/2894) orig_sp[:framestyle] = :axes end diff --git a/PlotsBase/src/output.jl b/PlotsBase/src/output.jl index c9bab7865..88822678c 100644 --- a/PlotsBase/src/output.jl +++ b/PlotsBase/src/output.jl @@ -163,8 +163,6 @@ Display a plot using the backends' gui window """ gui(plt::Plot = current()) = display(PlotsDisplay(), plt) -function inline end # for IJulia - function Base.display(::PlotsDisplay, plt::Plot) prepare_output(plt) _display(plt) @@ -172,7 +170,7 @@ end _do_plot_show(plt, showval::Bool) = showval && gui(plt) function _do_plot_show(plt, showval::Symbol) - showval === :gui && gui(plt) + showval ≡ :gui && gui(plt) showval in (:inline, :ijulia) && inline(plt) end @@ -184,10 +182,10 @@ const _best_html_output_type = # a backup for html... passes to svg or png depending on the html_output_format arg function _show(io::IO, ::MIME"text/html", plt::Plot) output_type = Symbol(plt.attr[:html_output_format]) - if output_type === :auto + if output_type ≡ :auto output_type = get(_best_html_output_type, backend_name(plt.backend), :svg) end - if output_type === :png + if output_type ≡ :png # @info "writing png to html output" print( io, @@ -195,10 +193,10 @@ function _show(io::IO, ::MIME"text/html", plt::Plot) base64encode(show, MIME("image/png"), plt), "\" />", ) - elseif output_type === :svg + elseif output_type ≡ :svg # @info "writing svg to html output" show(io, MIME("image/svg+xml"), plt) - elseif output_type === :txt + elseif output_type ≡ :txt show(io, MIME("text/plain"), plt) else error("only png or svg allowed. got: $(repr(output_type))") @@ -246,6 +244,20 @@ closeall() = closeall(backend()) Base.show(io::IO, m::MIME"application/prs.juno.plotpane+html", plt::Plot) = showjuno(io, MIME("text/html"), plt) +function inline end # for IJulia + +function hdf5plot_write end +function hdf5plot_read end + +""" +Add extra jupyter mimetypes to display_dict based on the plot backed. + +The default is nothing, except for plotly based backends, where it +adds data for `application/vnd.plotly.v1+json` that is used in +frontends like jupyterlab and nteract. +""" +_ijulia__extra_mime_info!(::Plot, out::Dict) = out + # Atom PlotPane function showjuno(io::IO, m, plt) dpi = plt[:dpi] @@ -270,4 +282,5 @@ _showjuno(io::IO, m::MIME"image/svg+xml", plt) = Base.showable(::MIME"application/prs.juno.plotpane+html", plt::Plot) = false _showjuno(io::IO, m, plt) = _show(io, m, plt) + # COV_EXCL_STOP diff --git a/PlotsBase/src/pipeline.jl b/PlotsBase/src/pipeline.jl index 93764ebbb..f66a957e0 100644 --- a/PlotsBase/src/pipeline.jl +++ b/PlotsBase/src/pipeline.jl @@ -10,7 +10,7 @@ function RecipesPipeline.warn_on_recipe_aliases!( ) pkeys = keys(plotattributes) for k in pkeys - if (dk = get(Commons._keyAliases, k, nothing)) !== nothing + if (dk = get(Commons._keyAliases, k, nothing)) ≢ nothing kv = RecipesPipeline.pop_kw!(plotattributes, k) dk ∈ pkeys || (plotattributes[dk] = kv) end @@ -63,7 +63,7 @@ end function _preprocess_userrecipe(kw::AKW) Commons._add_markershape(kw) - if get(kw, :permute, default(:permute)) !== :none + if get(kw, :permute, default(:permute)) ≢ :none l1, l2 = kw[:permute] for k in Commons._axis_attrs k1 = Commons._attrsymbolcache[l1][k] @@ -99,7 +99,7 @@ function _add_errorbar_kw(kw_list::Vector{KW}, kw::AKW) errors = (:xerror, :yerror, :zerror) if st ∉ errors for esym in errors - if get(kw, esym, nothing) !== nothing + if get(kw, esym, nothing) ≢ nothing # we make a copy of the KW and apply an errorbar recipe errkw = copy(kw) errkw[:seriestype] = esym @@ -162,7 +162,7 @@ function RecipesPipeline.process_sliced_series_attributes!(plt::PlotsBase.Plot, err_inds = findall(kw -> get(kw, :seriestype, :path) in (:xerror, :yerror, :zerror), kw_list) for ind in err_inds - if ind > 1 && get(kw_list[ind - 1], :seriestype, :path) === :scatter + if ind > 1 && get(kw_list[ind - 1], :seriestype, :path) ≡ :scatter tmp = copy(kw_list[ind]) kw_list[ind] = copy(kw_list[ind - 1]) kw_list[ind - 1] = tmp @@ -181,10 +181,10 @@ function RecipesPipeline.process_sliced_series_attributes!(plt::PlotsBase.Plot, kw[:ribbon] = map(rib, kw[:x]) end # convert a ribbon into a fillrange - if rib !== nothing + if rib ≢ nothing make_fillrange_from_ribbon(kw) # map fillrange if it's a Function - elseif fr !== nothing && fr isa Function + elseif fr ≢ nothing && fr isa Function kw[:fillrange] = map(fr, kw[:x]) end end @@ -214,7 +214,7 @@ function _plot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW}) end # handle inset subplots - if (insets = plt[:inset_subplots]) !== nothing + if (insets = plt[:inset_subplots]) ≢ nothing typeof(insets) <: AVec || (insets = [insets]) for inset in insets parent, bb = is_2tuple(inset) ? inset : (nothing, inset) @@ -247,10 +247,7 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW}) sps = get(kw, :subplot, :auto) sp = get_subplot( plt, - _cycle( - sps === :auto ? plt.subplots : plt.subplots[sps], - series_idx(kw_list, kw), - ), + _cycle(sps ≡ :auto ? plt.subplots : plt.subplots[sps], series_idx(kw_list, kw)), ) kw[:subplot] = sp @@ -290,7 +287,7 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW}) else get(sp_attrs, sp, KW()) end - PlotsPlots._update_subplot_attrs(plt, sp, attr, idx, false) + Plots._update_subplot_attrs(plt, sp, attr, idx, false) end # do we need to link any axes together? @@ -353,15 +350,15 @@ RecipesPipeline.is_seriestype_supported(plt::Plot, st) = is_seriestype_supported function RecipesPipeline.add_series!(plt::Plot, plotattributes) sp = _prepare_subplot(plt, plotattributes) - if (perm = plotattributes[:permute]) !== :none + if (perm = plotattributes[:permute]) ≢ :none letter1, letter2 = perm ms = plotattributes[:markershape] - if ms === :hline && (perm == (:x, :y) || perm == (:y, :x)) + if ms ≡ :hline && (perm == (:x, :y) || perm == (:y, :x)) plotattributes[:markershape] = :vline - elseif ms === :vline && (perm == (:x, :y) || perm == (:y, :x)) + elseif ms ≡ :vline && (perm == (:x, :y) || perm == (:y, :x)) plotattributes[:markershape] = :hline end - if plotattributes[:seriestype] === :bar # bar calls expand_extrema! in its recipe... + if plotattributes[:seriestype] ≡ :bar # bar calls expand_extrema! in its recipe... sp = plotattributes[:subplot] sp[get_attr_symbol(letter1, :axis)][:lims], sp[get_attr_symbol(letter2, :axis)][:lims] = @@ -381,16 +378,13 @@ end function _prepare_subplot(plt::Plot{T}, plotattributes::AKW) where {T} st::Symbol = plotattributes[:seriestype] sp::Subplot{T} = plotattributes[:subplot] - sp_idx = PlotsPlots.get_subplot_index(plt, sp) - PlotsPlots._update_subplot_attrs(plt, sp, plotattributes, sp_idx, true) + sp_idx = Plots.get_subplot_index(plt, sp) + Plots._update_subplot_attrs(plt, sp, plotattributes, sp_idx, true) st = _override_seriestype_check(plotattributes, st) # change to a 3d projection for this subplot? - if ( - RecipesPipeline.needs_3d_axes(st) || - (st === :quiver && plotattributes[:z] !== nothing) - ) + if (RecipesPipeline.needs_3d_axes(st) || (st ≡ :quiver && plotattributes[:z] ≢ nothing)) sp.attr[:projection] = "3d" end @@ -404,7 +398,7 @@ end function _expand_subplot_extrema(sp::Subplot, plotattributes::AKW, st::Symbol) # adjust extrema and discrete info - if st === :image + if st ≡ :image xmin, xmax = ignorenan_extrema(plotattributes[:x]) ymin, ymax = ignorenan_extrema(plotattributes[:y]) expand_extrema!(sp[:xaxis], (xmin, xmax)) @@ -426,11 +420,11 @@ function _add_the_series(plt, sp, plotattributes) plt[:extra_plot_kwargs] = get(kw, :plot, KW()) sp[:extra_kwargs] = get(kw, :subplot, KW()) plotattributes[:extra_kwargs] = get(kw, :series, KW()) - elseif kw === :plot + elseif kw ≡ :plot plt[:extra_plot_kwargs] = extra_kwargs - elseif kw === :subplot + elseif kw ≡ :subplot sp[:extra_kwargs] = extra_kwargs - elseif kw === :series + elseif kw ≡ :series plotattributes[:extra_kwargs] = extra_kwargs else throw(ArgumentError("Unsupported type for extra keyword arguments")) @@ -438,9 +432,9 @@ function _add_the_series(plt, sp, plotattributes) warn_on_unsupported(plt.backend, plotattributes) series = Series(plotattributes) push!(plt.series_list, series) - if (z_order = plotattributes[:z_order]) === :front + if (z_order = plotattributes[:z_order]) ≡ :front push!(sp.series_list, series) - elseif z_order === :back + elseif z_order ≡ :back pushfirst!(sp.series_list, series) elseif z_order isa Integer insert!(sp.series_list, z_order, series) diff --git a/PlotsBase/src/plot.jl b/PlotsBase/src/plot.jl index 963eea86c..892df16b0 100644 --- a/PlotsBase/src/plot.jl +++ b/PlotsBase/src/plot.jl @@ -5,7 +5,7 @@ mutable struct CurrentPlot end const CURRENT_PLOT = CurrentPlot(nothing) -isplotnull() = CURRENT_PLOT.nullableplot === nothing +isplotnull() = CURRENT_PLOT.nullableplot ≡ nothing """ current() @@ -182,7 +182,7 @@ function plot!( # first apply any args for the subplots for (idx, sp) in enumerate(plt.subplots) - PlotsPlots._update_subplot_attrs( + Plots._update_subplot_attrs( plt, sp, idx == ttl_idx ? KW() : plotattributes, @@ -254,8 +254,7 @@ function prepare_output(plt::Plot) force_minpad = get(plt, :force_minpad, ()) isempty(force_minpad) || for i in eachindex(plt.layout.grid) plt.layout.grid[i].minpad = Tuple( - i === nothing ? j : i for - (i, j) in zip(force_minpad, plt.layout.grid[i].minpad) + i ≡ nothing ? j : i for (i, j) in zip(force_minpad, plt.layout.grid[i].minpad) ) end diff --git a/PlotsBase/src/backends/plotly.jl b/PlotsBase/src/plotly.jl similarity index 92% rename from PlotsBase/src/backends/plotly.jl rename to PlotsBase/src/plotly.jl index 9a97182cf..e7c74d3a7 100644 --- a/PlotsBase/src/backends/plotly.jl +++ b/PlotsBase/src/plotly.jl @@ -8,28 +8,27 @@ import Statistics import UUIDs import JSON +using PlotUtils + +using PlotsBase.Colors: Colorant using PlotsBase.Annotations -using PlotsBase.Axes +using PlotsBase.DataSeries using PlotsBase.Colorbars -using PlotsBase.Colors: Colorant -using PlotsBase.Commons -using PlotsBase.Fonts -using PlotsBase.Fonts: PlotText -using PlotsBase.PlotMeasures -using PlotsBase.PlotsPlots -using PlotsBase.PlotsSeries -using PlotsBase.PlotUtils: PlotUtils, ColorGradient, rgba_string, rgb_string using PlotsBase.Subplots using PlotsBase.Surfaces +using PlotsBase.Commons +using PlotsBase.Plots +using PlotsBase.Fonts using PlotsBase.Ticks +using PlotsBase.Axes struct PlotlyBackend <: PlotsBase.AbstractBackend end + PlotsBase._backendType[:plotly] = PlotlyBackend PlotsBase._backendSymbol[PlotlyBackend] = :plotly - push!(PlotsBase._initialized_backends, :plotly) -PlotsBase.backend_name(::PlotlyBackend) = :plotly -PlotsBase.backend_package_name(::PlotlyBackend) = PlotsBase.backend_package_name(:plotly) + +eval(PlotsBase.backend_defines(:PlotlyBackend, :plotly)) const _plotly_attrs = PlotsBase.merge_with_base_supported([ :annotations, @@ -181,15 +180,6 @@ const _plotly_scales = [:identity, :log10] PlotsBase.default_output_format(plt::Plot{PlotlyBackend}) = "html" -for s in (:attr, :seriestype, :marker, :style, :scale) - f1 = Symbol("is_", s, "_supported") - f2 = Symbol("supported_", s, "s") - v = Symbol("_plotly_", s, "s") - eval(quote - PlotsBase.$f1(::PlotlyBackend, $s::Symbol) = $s in $v - PlotsBase.$f2(::PlotlyBackend) = sort(collect($v)) - end) -end # ---------------------------------------------------------------- function labelfunc(scale::Symbol, backend::PlotlyBackend) @@ -235,8 +225,8 @@ plotly_annotation_dict(x, y, ptxt::PlotText; xref = "paper", yref = "paper") = m plotly_annotation_dict(x, y, ptxt.str; xref = xref, yref = yref), KW( :font => plotly_font(ptxt.font), - :xanchor => ptxt.font.halign === :hcenter ? :center : ptxt.font.halign, - :yanchor => ptxt.font.valign === :vcenter ? :middle : ptxt.font.valign, + :xanchor => ptxt.font.halign ≡ :hcenter ? :center : ptxt.font.halign, + :yanchor => ptxt.font.valign ≡ :vcenter ? :middle : ptxt.font.valign, :rotation => -ptxt.font.rotation, ), ) @@ -253,13 +243,13 @@ plotly_annotation_dict( plotly_annotation_dict(x, y, z, ptxt.str; xref = xref, yref = yref, zref = zref), KW( :font => plotly_font(ptxt.font), - :xanchor => ptxt.font.halign === :hcenter ? :center : ptxt.font.halign, - :yanchor => ptxt.font.valign === :vcenter ? :middle : ptxt.font.valign, + :xanchor => ptxt.font.halign ≡ :hcenter ? :center : ptxt.font.halign, + :yanchor => ptxt.font.valign ≡ :vcenter ? :middle : ptxt.font.valign, :rotation => -ptxt.font.rotation, ), ) -plotly_scale(scale::Symbol) = scale === :log10 ? "log" : "-" +plotly_scale(scale::Symbol) = scale ≡ :log10 ? "log" : "-" function shrink_by(lo, sz, ratio) amt = 0.5(1 - ratio) * sz @@ -267,8 +257,8 @@ function shrink_by(lo, sz, ratio) end function plotly_apply_aspect_ratio(sp::Subplot, plotarea, pcts) - if (aspect_ratio = get_aspect_ratio(sp)) !== :none - aspect_ratio === :equal && (aspect_ratio = 1.0) + if (aspect_ratio = get_aspect_ratio(sp)) ≢ :none + aspect_ratio ≡ :equal && (aspect_ratio = 1.0) xmin, xmax = axis_limits(sp, :x) ymin, ymax = axis_limits(sp, :y) want_ratio = ((xmax - xmin) / (ymax - ymin)) / aspect_ratio @@ -307,30 +297,30 @@ function plotly_axis(axis, sp, anchor = nothing, domain = nothing) letter = axis[:letter] framestyle = sp[:framestyle] ax = KW( - :visible => framestyle !== :none, + :visible => framestyle ≢ :none, :title => axis[:guide], :showgrid => axis[:grid], :gridcolor => rgba_string(plot_color(axis[:foreground_color_grid], axis[:gridalpha])), :gridwidth => axis[:gridlinewidth], - :zeroline => framestyle === :zerolines, + :zeroline => framestyle ≡ :zerolines, :zerolinecolor => rgba_string(axis[:foreground_color_axis]), :showline => framestyle in (:box, :axes) && axis[:showaxis], :linecolor => rgba_string(plot_color(axis[:foreground_color_axis])), :ticks => - axis[:tick_direction] === :out ? "outside" : - axis[:tick_direction] === :in ? "inside" : "", - :mirror => framestyle === :box, + axis[:tick_direction] ≡ :out ? "outside" : + axis[:tick_direction] ≡ :in ? "inside" : "", + :mirror => framestyle ≡ :box, :showticklabels => axis[:showaxis], ) - anchor === nothing || (ax[:anchor] = anchor) - domain === nothing || (ax[:domain] = domain) + anchor ≡ nothing || (ax[:anchor] = anchor) + domain ≡ nothing || (ax[:domain] = domain) ax[:tickangle] = -axis[:rotation] ax[:type] = plotly_scale(axis[:scale]) lims = axis_limits(sp, letter) - if axis[:ticks] !== :native || axis[:lims] !== :auto + if axis[:ticks] ≢ :native || axis[:lims] ≢ :auto ax[:range] = map(RecipesPipeline.scale_func(axis[:scale]), lims) end @@ -343,13 +333,13 @@ function plotly_axis(axis, sp, anchor = nothing, domain = nothing) ax[:linecolor] = rgba_string(axis[:foreground_color_axis]) # ticks - if axis[:ticks] !== :native + if axis[:ticks] ≢ :native ticks = PlotsBase.get_ticks(sp, axis) ttype = PlotsBase.ticks_type(ticks) - if ttype === :ticks + if ttype ≡ :ticks ax[:tickmode] = "array" ax[:tickvals] = ticks - elseif ttype === :ticks_and_labels + elseif ttype ≡ :ticks_and_labels ax[:tickmode] = "array" ax[:tickvals], ax[:ticktext] = ticks end @@ -368,7 +358,7 @@ end function plotly_polaraxis(sp::Subplot, axis::Axis) ax = KW(:visible => axis[:showaxis], :showline => axis[:grid]) - if axis[:letter] === :x + if axis[:letter] ≡ :x ax[:range] = rad2deg.(axis_limits(sp, :x)) else ax[:range] = axis_limits(sp, :y) @@ -397,13 +387,13 @@ function plotly_layout(plt::Plot) if sp[:title] != "" bb = plotarea(sp) tpos = sp[:titlelocation] - if tpos === :left + if tpos ≡ :left xmm, ymm = left(bb), top(bbox(sp)) halign, valign = :left, :top - elseif tpos === :center + elseif tpos ≡ :center xmm, ymm = 0.5(left(bb) + right(bb)), top(bbox(sp)) halign, valign = :hcenter, :top - elseif tpos === :right + elseif tpos ≡ :right xmm, ymm = right(bb), top(bbox(sp)) halign, valign = :right, :top else @@ -524,9 +514,9 @@ function plotly_layout(plt::Plot) end function plotly_add_legend!(plotattributes_out::KW, sp::Subplot) - plotattributes_out[:showlegend] = sp[:legend_position] !== :none + plotattributes_out[:showlegend] = sp[:legend_position] ≢ :none legend_position = plotly_legend_pos(sp[:legend_position]) - sp[:legend_position] === :none && return + sp[:legend_position] ≡ :none && return plotattributes_out[:legend] = KW( :bgcolor => rgba_string(sp[:legend_background_color]), :bordercolor => rgba_string(sp[:legend_foreground_color]), @@ -539,7 +529,7 @@ function plotly_add_legend!(plotattributes_out::KW, sp::Subplot) :x => legend_position.coords[1], :y => legend_position.coords[2], :title => KW( - :text => sp[:legend_title] === nothing ? "" : string(sp[:legend_title]), + :text => sp[:legend_title] ≡ nothing ? "" : string(sp[:legend_title]), :font => plotly_font(legendtitlefont(sp)), ), ) @@ -599,7 +589,7 @@ function plotly_legend_pos(v::Tuple{S,Symbol}) where {S<:Real} xanchors = ["left", "center", "right"] yanchors = ["bottom", "middle", "top"] - if v[2] === :inner + if v[2] ≡ :inner rect = 0.07, 0.5, 1.0, 0.07, 0.52, 1.0 xanchor = xanchors[legend_anchor_index(c)] yanchor = yanchors[legend_anchor_index(s)] @@ -632,7 +622,10 @@ function plotly_colorscale(cg::PlotUtils.CategoricalColorGradient, α = nothing) cinds = repeat(1:n, inner = 2) vinds = vcat((i:(i + 1) for i in 1:n)...) map( - i -> [cg.values[vinds[i]], rgba_string(plot_color(color_list(cg)[cinds[i]], α))], + i -> [ + cg.values[vinds[i]], + rgba_string(plot_color(PlotsBase.color_list(cg)[cinds[i]], α)), + ], eachindex(cinds), ) end @@ -678,7 +671,7 @@ end function plotly_data(series::Series, letter::Symbol, data) axis = series[:subplot][get_attr_symbol(letter, :axis)] - data = if axis[:ticks] === :native && data !== nothing + data = if axis[:ticks] ≡ :native && data ≢ nothing plotly_native_data(axis, data) else data @@ -690,7 +683,7 @@ function plotly_data(series::Series, letter::Symbol, data) plotly_data(data) end end -plotly_data(v) = v !== nothing ? collect(v) : v +plotly_data(v) = v ≢ nothing ? collect(v) : v plotly_data(v::AbstractArray) = v plotly_data(surf::Surface) = surf.surf plotly_data(v::AbstractArray{R}) where {R<:Rational} = float(v) @@ -730,7 +723,7 @@ function plotly_series(plt::Plot, series::Series) sp = series[:subplot] clims = get_clims(sp, series) - (st = series[:seriestype]) === :shape && return plotly_series_shapes(plt, series, clims) + (st = series[:seriestype]) ≡ :shape && return plotly_series_shapes(plt, series, clims) plotattributes_out = KW() @@ -748,8 +741,8 @@ function plotly_series(plt::Plot, series::Series) end plotattributes_out[:showlegend] = should_add_to_legend(series) - if st === :straightline - x, y = straightline_data(series, 100) + if st ≡ :straightline + x, y = PlotsBase.straightline_data(series, 100) z = series[:z] else x, y, z = series[:x], series[:y], series[:z] @@ -763,7 +756,7 @@ function plotly_series(plt::Plot, series::Series) plotattributes_out[:name] = series[:label] isscatter = st in (:scatter, :scatter3d, :scattergl) - hasmarker = isscatter || series[:markershape] !== :none + hasmarker = isscatter || series[:markershape] ≢ :none hasline = st in (:path, :path3d, :straightline) hasfillrange = st in (:path, :scatter, :scattergl, :straightline) && @@ -779,16 +772,16 @@ function plotly_series(plt::Plot, series::Series) if st in (:path, :scatter, :scattergl, :straightline, :path3d, :scatter3d) return plotly_series_segments(series, plotattributes_out, x, y, z, clims) - elseif st === :heatmap - x = heatmap_edges(x, sp[:xaxis][:scale]) - y = heatmap_edges(y, sp[:yaxis][:scale]) + elseif st ≡ :heatmap + x = PlotsBase.heatmap_edges(x, sp[:xaxis][:scale]) + y = PlotsBase.heatmap_edges(y, sp[:yaxis][:scale]) plotattributes_out[:type] = "heatmap" plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z plotattributes_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) plotattributes_out[:showscale] = hascolorbar(sp) - elseif st === :contour + elseif st ≡ :contour filled = isfilledcontour(series) plotattributes_out[:type] = "contour" plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z @@ -827,7 +820,7 @@ function plotly_series(plt::Plot, series::Series) elseif st in (:surface, :wireframe) plotattributes_out[:type] = "surface" plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z - if st === :wireframe + if st ≡ :wireframe plotattributes_out[:hidesurface] = true wirelines = KW( :show => true, @@ -842,16 +835,16 @@ function plotly_series(plt::Plot, series::Series) plotattributes_out[:colorscale] = plotly_colorscale(series[:fillcolor], series[:fillalpha]) plotattributes_out[:opacity] = series[:fillalpha] - if series[:fill_z] !== nothing + if series[:fill_z] ≢ nothing plotattributes_out[:surfacecolor] = handle_surface(series[:fill_z]) end plotattributes_out[:showscale] = hascolorbar(sp) end - elseif st === :mesh3d + elseif st ≡ :mesh3d plotattributes_out[:type] = "mesh3d" plotattributes_out[:x], plotattributes_out[:y], plotattributes_out[:z] = x, y, z - if series[:connections] !== nothing + if series[:connections] ≢ nothing if typeof(series[:connections]) <: Tuple{Array,Array,Array} # 0-based indexing i, j, k = series[:connections] @@ -888,7 +881,7 @@ function plotly_series(plt::Plot, series::Series) plotattributes_out[:color] = rgba_string(plot_color(series[:fillcolor], series[:fillalpha])) plotattributes_out[:opacity] = series[:fillalpha] - if series[:fill_z] !== nothing + if series[:fill_z] ≢ nothing plotattributes_out[:surfacecolor] = handle_surface(series[:fill_z]) end plotattributes_out[:showscale] = hascolorbar(sp) @@ -960,7 +953,7 @@ function plotly_series_shapes(plt::Plot, series::Series, clims) x, y = ( plotly_data(series, letter, data) for - (letter, data) in zip((:x, :y), shape_data(series, 100)) + (letter, data) in zip((:x, :y), PlotsBase.shape_data(series, 100)) ) for (k, segment) in enumerate(segments) @@ -995,11 +988,11 @@ function plotly_series_shapes(plt::Plot, series::Series, clims) plotly_adjust_hover_label!(plotattributes_out, _cycle(series[:hover], i)) plotattributes_outs[k] = merge(plotattributes_out, series[:extra_kwargs]) end - if series[:fill_z] !== nothing + if series[:fill_z] ≢ nothing push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :fill)) - elseif series[:line_z] !== nothing + elseif series[:line_z] ≢ nothing push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :line)) - elseif series[:marker_z] !== nothing + elseif series[:marker_z] ≢ nothing push!( plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :marker), @@ -1012,7 +1005,7 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z st = series[:seriestype] sp = series[:subplot] isscatter = st in (:scatter, :scatter3d, :scattergl) - hasmarker = isscatter || series[:markershape] !== :none + hasmarker = isscatter || series[:markershape] ≢ :none hasline = st in (:path, :path3d, :straightline) hasfillrange = st in (:path, :scatter, :scattergl, :straightline) && @@ -1032,7 +1025,7 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z # set the type if st in (:path, :scatter, :scattergl, :straightline) - plotattributes_out[:type] = st === :scattergl ? "scattergl" : "scatter" + plotattributes_out[:type] = st ≡ :scattergl ? "scattergl" : "scatter" plotattributes_out[:mode] = if hasmarker hasline ? "lines+markers" : "markers" else @@ -1071,7 +1064,7 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z mcolor = rgba_string( plot_color(get_markercolor(series, clims, i), get_markeralpha(series, i)), ) - mcolor_next = if (mz = series[:marker_z]) !== nothing && i < length(mz) + mcolor_next = if (mz = series[:marker_z]) ≢ nothing && i < length(mz) plot_color( get_markercolor(series, clims, i + 1), get_markeralpha(series, i + 1), @@ -1113,11 +1106,11 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z plot_color(get_linecolor(series, clims, i), get_linealpha(series, i)), ), :width => get_linewidth(series, i), - :shape => if st === :steppre + :shape => if st ≡ :steppre "vh" - elseif st === :stepmid + elseif st ≡ :stepmid "hvh" - elseif st === :steppost + elseif st ≡ :steppost "hv" else "linear" @@ -1154,7 +1147,7 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z # if fillrange is a tuple with upper and lower limit, plotattributes_out_fillrange # is the series that will do the filling plotattributes_out_fillrange[:x], plotattributes_out_fillrange[:y] = - concatenate_fillrange(x[rng], series[:fillrange]) + PlotsBase.concatenate_fillrange(x[rng], series[:fillrange]) plotattributes_out_fillrange[:line][:width] = 0 delete!(plotattributes_out, :fill) delete!(plotattributes_out, :fillcolor) @@ -1168,11 +1161,11 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z plotattributes_outs[k] = merge(plotattributes_outs[k], series[:extra_kwargs]) end - if series[:line_z] !== nothing + if series[:line_z] ≢ nothing push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :line)) - elseif series[:fill_z] !== nothing + elseif series[:fill_z] ≢ nothing push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :fill)) - elseif series[:marker_z] !== nothing + elseif series[:marker_z] ≢ nothing push!( plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :marker), @@ -1215,7 +1208,7 @@ plotly_polar!(plotattributes_out::KW, series::Series) = end function plotly_adjust_hover_label!(plotattributes_out::KW, hover) - if hover === nothing + if hover ≡ nothing return elseif all(in([:none, false]), hover) plotattributes_out[:hoverinfo] = "none" @@ -1272,7 +1265,7 @@ function plotly_html_head(plt::Plot) end function plotly_html_body(plt, style = nothing) - if style === nothing + if style ≡ nothing w, h = plt[:size] style = "width:$(w)px;height:$(h)px;" end @@ -1323,4 +1316,12 @@ PlotsBase._show(io::IO, ::MIME"text/html", plt::Plot{PlotlyBackend}) = PlotsBase._display(plt::Plot{PlotlyBackend}) = standalone_html_window(plt) -end # module +function _ijulia__extra_mime_info!(plt::Plot{PlotlyBackend}, out::Dict) + out["application/vnd.plotly.v1+json"] = + Dict(:data => plotly_series(plt), :layout => plotly_layout(plt)) + out +end + +end # module + +using .Plotly diff --git a/PlotsBase/src/preferences.jl b/PlotsBase/src/preferences.jl new file mode 100644 index 000000000..e58f5394a --- /dev/null +++ b/PlotsBase/src/preferences.jl @@ -0,0 +1,49 @@ +# from github.com/JuliaPackaging/Preferences.jl/blob/master/README.md: +# "Preferences that are accessed during compilation are automatically marked as compile-time preferences" +# ==> this must always be done during precompilation, otherwise +# the cache will not invalidate when preferences change +const DEFAULT_BACKEND = lowercase(load_preference(PlotsBase, "default_backend", "gr")) + +function default_backend() + # environment variable preempts the `Preferences` based mechanism + name = get(ENV, "PLOTSBASE_DEFAULT_BACKEND", DEFAULT_BACKEND) |> lowercase |> Symbol + backend(name) +end + +function set_default_backend!( + backend::Union{Nothing,AbstractString,Symbol} = nothing; + force = true, + kw..., +) + if backend ≡ nothing + delete_preferences!(PlotsBase, "default_backend"; force, kw...) + else + # NOTE: `_check_installed` already throws a warning + if (value = lowercase(string(backend))) |> PlotsBase._check_installed ≢ nothing + set_preferences!(PlotsBase, "default_backend" => value; force, kw...) + end + end + nothing +end + +function diagnostics(io::IO = stdout) + origin = if has_preference(PlotsBase, "default_backend") + "`Preferences`" + elseif haskey(ENV, "PLOTSBASE_DEFAULT_BACKEND") + "environment variable" + else + "fallback" + end + if (be = backend_name()) ≡ :none + @info "no `PlotsBase` backends currently initialized" + else + pkg_name = string(PlotsBase.backend_package_name(be)) + @info "selected `PlotsBase` backend: $pkg_name, from $origin" + Pkg.status( + ["PlotsBase", "RecipesBase", "RecipesPipeline", pkg_name]; + mode = Pkg.PKGMODE_MANIFEST, + io, + ) + end + nothing +end diff --git a/PlotsBase/src/recipes.jl b/PlotsBase/src/recipes.jl index 0431d2826..eeccb3623 100644 --- a/PlotsBase/src/recipes.jl +++ b/PlotsBase/src/recipes.jl @@ -13,7 +13,7 @@ function seriestype_supported(pkg::AbstractBackend, st::Symbol) supported = true for dep in _series_recipe_deps[st] - if seriestype_supported(pkg, dep) === :no + if seriestype_supported(pkg, dep) ≡ :no supported = false break end @@ -29,7 +29,7 @@ end function all_seriestypes() sts = Set{Symbol}(keys(_series_recipe_deps)) for bsym in _initialized_backends - be = _backend_instance(bsym) + be = backend_instance(bsym) sts = union(sts, Set{Symbol}(supported_seriestypes(be))) end sts |> collect |> sort @@ -199,11 +199,11 @@ function make_steps(x::AbstractArray, st, even) for i in 2:n xindex = xstartindex - 1 + i idx = 2i - 1 - if st === :mid + if st ≡ :mid newx[idx] = newx[idx - 1] = (x[xindex] + x[xindex - 1]) / 2 else newx[idx] = x[xindex] - newx[idx - 1] = x[st === :pre ? xindex : xindex - 1] + newx[idx - 1] = x[st ≡ :pre ? xindex : xindex - 1] end end even && (newx[end] = x[end]) @@ -223,7 +223,7 @@ make_steps(t::Tuple, st, even) = Tuple(make_steps(ti, st, even) for ti in t) plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :pre, false) # create a secondary series for the markers - if plotattributes[:markershape] !== :none + if plotattributes[:markershape] ≢ :none @series begin seriestype := :scatter x := x @@ -248,7 +248,7 @@ end plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :post, true) # create a secondary series for the markers - if plotattributes[:markershape] !== :none + if plotattributes[:markershape] ≢ :none @series begin seriestype := :scatter x := x @@ -273,7 +273,7 @@ end plotattributes[:fillrange] = make_steps(plotattributes[:fillrange], :post, false) # create a secondary series for the markers - if plotattributes[:markershape] !== :none + if plotattributes[:markershape] ≢ :none @series begin seriestype := :scatter x := x @@ -294,19 +294,19 @@ 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 + if (fr = plotattributes[:fillrange]) ≡ nothing sp = plotattributes[:subplot] - fr = if sp[:yaxis][:scale] === :identity + 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)) + 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)) rng = (3i - 2):(3i) newx[rng] = [xi, xi, NaN] - if z !== nothing + if z ≢ nothing newy[rng] = [yi, yi, NaN] newz[rng] = [_cycle(fr, i), zi, NaN] else @@ -315,27 +315,27 @@ end end x := newx y := newy - if z !== nothing + if z ≢ nothing z := newz end fillrange := nothing seriestype := :path if ( - plotattributes[:linecolor] === :auto && - plotattributes[:marker_z] !== nothing && - plotattributes[:line_z] === nothing + plotattributes[:linecolor] ≡ :auto && + plotattributes[:marker_z] ≢ nothing && + plotattributes[:line_z] ≡ nothing ) line_z := plotattributes[:marker_z] end # create a primary series for the markers - if plotattributes[:markershape] !== :none + if plotattributes[:markershape] ≢ :none primary := false @series begin seriestype := :scatter x := x y := y - if z !== nothing + if z ≢ nothing z := z end primary := true @@ -366,35 +366,35 @@ end # create segmented bezier curves in place of line segments @recipe function f(::Type{Val{:curves}}, x, y, z; npoints = 30) # COV_EXCL_LINE - args = z !== nothing ? (x, y, z) : (x, y) + args = z ≢ nothing ? (x, y, z) : (x, y) newx, newy = zeros(0), zeros(0) - newfr = (fr = plotattributes[:fillrange]) !== nothing ? zeros(0) : nothing - newz = z !== nothing ? zeros(0) : nothing + newfr = (fr = plotattributes[:fillrange]) ≢ nothing ? zeros(0) : nothing + newz = z ≢ nothing ? zeros(0) : nothing # for each line segment (point series with no NaNs), convert it into a bezier curve # where the points are the control points of the curve - for rng in PlotsSeries.iter_segments(args...) + 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)) - if z !== nothing + if z ≢ nothing nanappend!(newz, map(t -> bezier_value(_cycle(z, rng), t), ts)) end - if fr !== nothing + if fr ≢ nothing nanappend!(newfr, map(t -> bezier_value(_cycle(fr, rng), t), ts)) end end x := newx y := newy - if z === nothing + if z ≡ nothing seriestype := :path else seriestype := :path3d z := newz end - if fr !== nothing + if fr ≢ nothing fillrange := newfr end () @@ -422,7 +422,7 @@ end # compute half-width of bars bw = plotattributes[:bar_width] - hw = if bw === nothing + hw = if bw ≡ nothing 0.5Commons._bar_width * if nx > 1 ignorenan_minimum(filter(x -> x > 0, diff(sort(procx)))) else @@ -433,7 +433,7 @@ end end # make fillto a vector... default fills to 0 - if (fillto = plotattributes[:fillrange]) === nothing + if (fillto = plotattributes[:fillrange]) ≡ nothing fillto = 0 end if yscale in _log_scales && !all(_is_positive, fillto) @@ -587,7 +587,7 @@ end @recipe function f(::Type{Val{:barbins}}, x, y, z) # COV_EXCL_LINE edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, x, y) - if plotattributes[:bar_width] === nothing + if plotattributes[:bar_width] ≡ nothing bar_width := diff(edge) end x := _bin_centers(edge) @@ -634,7 +634,7 @@ function _stepbins_path(edge, weights, baseline::Real, xscale::Symbol, yscale::S last_w = eltype(weights)(NaN) - while it_tuple_e !== nothing && it_tuple_w !== nothing + while it_tuple_e ≢ nothing && it_tuple_w ≢ nothing b, it_state_e = it_tuple_e w, it_state_w = it_tuple_w @@ -678,7 +678,7 @@ end xpts, ypts = _stepbins_path(edge, weights, baseline, xscale, yscale) # create a secondary series for the markers - if plotattributes[:markershape] !== :none + if plotattributes[:markershape] ≢ :none @series begin seriestype := :scatter x := _bin_centers(edge) @@ -729,19 +729,19 @@ function _auto_binning_nbins( end v = vs[dim] - mode === :auto && (mode = :fd) + mode ≡ :auto && (mode = :fd) - if mode === :sqrt # Square-root choice + if mode ≡ :sqrt # Square-root choice _cl(sqrt(n_samples)) - elseif mode === :sturges # Sturges' formula + elseif mode ≡ :sturges # Sturges' formula _cl(log2(n_samples) + 1) - elseif mode === :rice # Rice Rule + elseif mode ≡ :rice # Rice Rule _cl(2 * nd) - elseif mode === :scott # Scott's normal reference rule + elseif mode ≡ :scott # Scott's normal reference rule _cl(_span(v) / (3.5 * std(v) / nd)) - elseif mode === :fd # Freedman–Diaconis rule + elseif mode ≡ :fd # Freedman–Diaconis rule _cl(_span(v) / (2 * _iqr(v) / nd)) - elseif mode === :wand + elseif mode ≡ :wand wand_edges(v) # this makes this function not type stable, but the type instability does not propagate else error("Unknown auto-binning mode $mode") @@ -782,7 +782,7 @@ function _make_hist( localvs = _filternans(vs) edges = _hist_edges(localvs, binning) h = float( - weights === nothing ? + weights ≡ nothing ? StatsBase.fit(StatsBase.Histogram, localvs, edges, closed = :left) : StatsBase.fit( StatsBase.Histogram, @@ -856,7 +856,7 @@ end ) seriestype := get(st_map, plotattributes[:seriestype], plotattributes[:seriestype]) - if plotattributes[:seriestype] === :scatterbins + if plotattributes[:seriestype] ≡ :scatterbins # Workaround, error bars currently not set correctly by scatterbins edge, weights, xscale, yscale, baseline = _preprocess_binlike(plotattributes, h.edges[1], h.weights) @@ -883,7 +883,7 @@ end float_weights = float(weights) if !plotattributes[:show_empty_bins] - if float_weights === weights + if float_weights ≡ weights float_weights = deepcopy(float_weights) end for (i, c) in enumerate(float_weights) @@ -948,7 +948,7 @@ end @recipe function f(::Type{Val{:mesh3d}}, x, y, z) # COV_EXCL_LINE # As long as no i,j,k are supplied this should work with PyPlot and GR seriestype := :surface - if plotattributes[:connections] !== nothing + if plotattributes[:connections] ≢ nothing "Giving triangles using the connections argument is only supported on Plotly backend." |> ArgumentError |> throw @@ -961,7 +961,7 @@ end @recipe function f(::Type{Val{:scatter3d}}, x, y, z) # COV_EXCL_LINE seriestype := :path3d - if plotattributes[:markershape] === :none + if plotattributes[:markershape] ≡ :none markershape := :circle end linewidth := 0 @@ -977,7 +977,7 @@ lens!(args...; kwargs...) = plot!(args...; seriestype = :lens, kwargs...) export lens! @recipe function f(::Type{Val{:lens}}, plt::AbstractPlot) # COV_EXCL_LINE sp_index, inset_bbox = plotattributes[:inset_subplots] - width(inset_bbox) isa Measures.Length{:w,<:Real} || + width(inset_bbox) isa Commons.Length{:w,<:Real} || throw(ArgumentError("Inset bounding box needs to in relative coordinates.")) sp = plt.subplots[sp_index] xscale = sp[:xaxis][:scale] @@ -1084,9 +1084,9 @@ Commons.@attributes function error_style!(plotattributes::AKW) RecipesPipeline.reset_kw!(plotattributes, :marker_z) haskey(plotattributes, :line_z) && RecipesPipeline.reset_kw!(plotattributes, :line_z) - msc = if (msc = plotattributes[:markerstrokecolor]) === :match + msc = if (msc = plotattributes[:markerstrokecolor]) ≡ :match plotattributes[:subplot][:foreground_color_subplot] - elseif msc === :auto + elseif msc ≡ :auto get_series_color( plotattributes[:linecolor], plotattributes[:subplot], @@ -1137,13 +1137,13 @@ clamp_to_eps!(ary) = (replace!(x -> x <= 0.0 ? Base.eps(Float64) : x, ary); noth Commons.error_style!(plotattributes) markershape := :vline xerr = error_zipit(plotattributes[:xerror]) - if z === nothing + if z ≡ nothing plotattributes[:x], plotattributes[:y] = error_coords(xerr, x, y) else plotattributes[:x], plotattributes[:y], plotattributes[:z] = error_coords(xerr, x, y, z) end - if :xscale ∈ keys(plotattributes) && plotattributes[:xscale] === :log10 + if :xscale ∈ keys(plotattributes) && plotattributes[:xscale] ≡ :log10 clamp_to_eps!(plotattributes[:x]) end () @@ -1154,13 +1154,13 @@ end Commons.error_style!(plotattributes) markershape := :hline yerr = error_zipit(plotattributes[:yerror]) - if z === nothing + if z ≡ nothing plotattributes[:y], plotattributes[:x] = error_coords(yerr, y, x) else plotattributes[:y], plotattributes[:x], plotattributes[:z] = error_coords(yerr, y, x, z) end - if :yscale ∈ keys(plotattributes) && plotattributes[:yscale] === :log10 + if :yscale ∈ keys(plotattributes) && plotattributes[:yscale] ≡ :log10 clamp_to_eps!(plotattributes[:y]) end () @@ -1170,12 +1170,12 @@ end @recipe function f(::Type{Val{:zerror}}, x, y, z) # COV_EXCL_LINE Commons.error_style!(plotattributes) markershape := :hline - if z !== nothing + if z ≢ nothing zerr = error_zipit(plotattributes[:zerror]) plotattributes[:z], plotattributes[:x], plotattributes[:y] = error_coords(zerr, z, x, y) end - if :zscale ∈ keys(plotattributes) && plotattributes[:zscale] === :log10 + if :zscale ∈ keys(plotattributes) && plotattributes[:zscale] ≡ :log10 clamp_to_eps!(plotattributes[:z]) end () @@ -1456,7 +1456,7 @@ end @recipe f(x::AVec, ohlc::AVec{NTuple{N,<:Number}}) where {N} = x, map(t -> OHLC(t...), ohlc) @recipe f(xyuv::AVec{NTuple}) = - get(plotattributes, :seriestype, :path) === :ohlc ? map(t -> OHLC(t...), xyuv) : + get(plotattributes, :seriestype, :path) ≡ :ohlc ? map(t -> OHLC(t...), xyuv) : RecipesPipeline.unzip(xyuv) @recipe function f(x::AVec, v::AVec{OHLC}) # COV_EXCL_LINE diff --git a/PlotsBase/src/utils.jl b/PlotsBase/src/utils.jl index ef38e1baa..d6a37db48 100644 --- a/PlotsBase/src/utils.jl +++ b/PlotsBase/src/utils.jl @@ -76,11 +76,11 @@ function _update_series_attributes!(plotattributes::AKW, plt::Plot, sp::Subplot) # update alphas for asym in (:linealpha, :markeralpha, :fillalpha) - if plotattributes[asym] === nothing + if plotattributes[asym] ≡ nothing plotattributes[asym] = plotattributes[:seriesalpha] end end - if plotattributes[:markerstrokealpha] === nothing + if plotattributes[:markerstrokealpha] ≡ nothing plotattributes[:markerstrokealpha] = plotattributes[:markeralpha] end @@ -92,13 +92,13 @@ function _update_series_attributes!(plotattributes::AKW, plt::Plot, sp::Subplot) # update other colors (`linecolor`, `markercolor`, `fillcolor`) <- for grep for s in (:line, :marker, :fill) csym, asym = Symbol(s, :color), Symbol(s, :alpha) - plotattributes[csym] = if plotattributes[csym] === :auto - plot_color(if Commons.has_black_border_for_default(stype) && s === :line - sp[:foreground_color_subplot] - else - scolor - end) - elseif plotattributes[csym] === :match + plotattributes[csym] = if plotattributes[csym] ≡ :auto + plot_color(if Commons.has_black_border_for_default(stype) && s ≡ :line + sp[:foreground_color_subplot] + else + scolor + end) + elseif plotattributes[csym] ≡ :match plot_color(scolor) else get_series_color(plotattributes[csym], sp, plotIndex, stype) @@ -106,29 +106,29 @@ function _update_series_attributes!(plotattributes::AKW, plt::Plot, sp::Subplot) end # update markerstrokecolor - plotattributes[:markerstrokecolor] = if plotattributes[:markerstrokecolor] === :match + plotattributes[:markerstrokecolor] = if plotattributes[:markerstrokecolor] ≡ :match plot_color(sp[:foreground_color_subplot]) - elseif plotattributes[:markerstrokecolor] === :auto + elseif plotattributes[:markerstrokecolor] ≡ :auto get_series_color(plotattributes[:markercolor], sp, plotIndex, stype) else get_series_color(plotattributes[:markerstrokecolor], sp, plotIndex, stype) end # if marker_z, fill_z or line_z are set, ensure we have a gradient - if plotattributes[:marker_z] !== nothing + if plotattributes[:marker_z] ≢ nothing Commons.ensure_gradient!(plotattributes, :markercolor, :markeralpha) end - if plotattributes[:line_z] !== nothing + if plotattributes[:line_z] ≢ nothing Commons.ensure_gradient!(plotattributes, :linecolor, :linealpha) end - if plotattributes[:fill_z] !== nothing + if plotattributes[:fill_z] ≢ nothing Commons.ensure_gradient!(plotattributes, :fillcolor, :fillalpha) end # scatter plots don't have a line, but must have a shape if plotattributes[:seriestype] in (:scatter, :scatterbins, :scatterhist, :scatter3d) plotattributes[:linewidth] = 0 - if plotattributes[:markershape] === :none + if plotattributes[:markershape] ≡ :none plotattributes[:markershape] = :circle end end @@ -215,7 +215,7 @@ heatmap_edges( scale::Symbol = :identity, isedges::Bool = false, ispolar::Bool = false, -) = _heatmap_edges(Val(scale === :identity), v, scale, isedges, ispolar) +) = _heatmap_edges(Val(scale ≡ :identity), v, scale, isedges, ispolar) function heatmap_edges( x::AVec, @@ -239,8 +239,8 @@ function heatmap_edges( ArgumentError |> throw ( - _heatmap_edges(Val(xscale === :identity), x, xscale, isedges, false), - _heatmap_edges(Val(yscale === :identity), y, yscale, isedges, ispolar), # special handle for `r` in polar plots + _heatmap_edges(Val(xscale ≡ :identity), x, xscale, isedges, false), + _heatmap_edges(Val(yscale ≡ :identity), y, yscale, isedges, ispolar), # special handle for `r` in polar plots ) end @@ -270,27 +270,11 @@ end isijulia() = :IJulia in nameof.(collect(values(Base.loaded_modules))) isatom() = :Atom in nameof.(collect(values(Base.loaded_modules))) -istuple(::Tuple) = true -istuple(::Any) = false -isvector(::AVec) = true -isvector(::Any) = false -ismatrix(::AMat) = true -ismatrix(::Any) = false -isscalar(::Real) = true -isscalar(::Any) = false - -is_2tuple(v) = typeof(v) <: Tuple && length(v) == 2 - -ticks_type(ticks::AVec{<:Real}) = :ticks -ticks_type(ticks::AVec{<:AbstractString}) = :labels -ticks_type(ticks::Tuple{<:Union{AVec,Tuple},<:Union{AVec,Tuple}}) = :ticks_and_labels -ticks_type(ticks) = :invalid - limsType(lims::Tuple{<:Real,<:Real}) = :limits -limsType(lims::Symbol) = lims === :auto ? :auto : :invalid +limsType(lims::Symbol) = lims ≡ :auto ? :auto : :invalid limsType(lims) = :invalid -isautop(sp::Subplot) = sp[:projection_type] === :auto +isautop(sp::Subplot) = sp[:projection_type] ≡ :auto isortho(sp::Subplot) = sp[:projection_type] ∈ (:ortho, :orthographic) ispersp(sp::Subplot) = sp[:projection_type] ∈ (:persp, :perspective) @@ -305,7 +289,7 @@ nanappend!(a::AbstractVector, b) = (push!(a, NaN); append!(a, b); nothing) function nansplit(v::AVec) vs = Vector{eltype(v)}[] while true - if (idx = findfirst(isnan, v)) === nothing + if (idx = findfirst(isnan, v)) ≡ nothing # no nans push!(vs, v) break @@ -339,7 +323,7 @@ function make_fillrange_from_ribbon(kw::AKW) rib1, rib2 = -first(rib), last(rib) # kw[:ribbon] = nothing kw[:fillrange] = make_fillrange_side(y, rib1), make_fillrange_side(y, rib2) - (get(kw, :fillalpha, nothing) === nothing) && (kw[:fillalpha] = 0.5) + (get(kw, :fillalpha, nothing) ≡ nothing) && (kw[:fillalpha] = 0.5) end #turn tuple of fillranges to one path @@ -406,8 +390,8 @@ function Commons.preprocess_attributes!(plotattributes::AKW) if treats_y_as_x(get(plotattributes, :seriestype, :path)) xformatter = get(plotattributes, :xformatter, :auto) yformatter = get(plotattributes, :yformatter, :auto) - yformatter !== :auto && (plotattributes[:xformatter] = yformatter) - xformatter === :auto && + yformatter ≢ :auto && (plotattributes[:xformatter] = yformatter) + xformatter ≡ :auto && haskey(plotattributes, :yformatter) && pop!(plotattributes, :yformatter) end @@ -475,7 +459,7 @@ function Commons.preprocess_attributes!(plotattributes::AKW) end # handle axes args for k in Commons._axis_attrs - if haskey(plotattributes, k) && k !== :link + if haskey(plotattributes, k) && k ≢ :link v = plotattributes[k] for letter in (:x, :y, :z) lk = get_attr_symbol(letter, k) @@ -515,7 +499,7 @@ function Commons.preprocess_attributes!(plotattributes::AKW) if haskey(plotattributes, :markershape) plotattributes[:markershape] = Commons._replace_markershape(plotattributes[:markershape]) - if plotattributes[:markershape] === :none && + if plotattributes[:markershape] ≡ :none && get(plotattributes, :seriestype, :path) in (:scatter, :scatterbins, :scatterhist, :scatter3d) #the default should be :auto, not :none, so that :none can be set explicitly and would be respected plotattributes[:markershape] = :circle @@ -594,33 +578,31 @@ end ``` """ function with(f::Function, args...; scalefonts = nothing, kw...) - newdefs = KW(kw) + new_defs = KW(kw) if :canvas in args - newdefs[:xticks] = nothing - newdefs[:yticks] = nothing - newdefs[:grid] = false - newdefs[:legend_position] = false + new_defs[:xticks] = nothing + new_defs[:yticks] = nothing + new_defs[:grid] = false + new_defs[:legend_position] = false end # dict to store old and new keyword args for anything that changes - olddefs = KW() - for k in keys(newdefs) - olddefs[k] = default(k) + old_defs = KW() + for k in keys(new_defs) + old_defs[k] = default(k) end # save the backend - oldbackend = CURRENT_BACKEND.sym + old_backend = backend_name() for arg in args - # change backend? - if arg isa Symbol - if arg ∈ backends() - if (pkg = backend_package_name(arg)) ≢ nothing # :plotly - @eval Main import $pkg - end - backend(arg) + # 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 # TODO: generalize this strategy to allow args as much as possible @@ -629,57 +611,57 @@ function with(f::Function, args...; scalefonts = nothing, kw...) k = :legend if arg in (k, :leg) - olddefs[k] = default(k) - newdefs[k] = true + old_defs[k] = default(k) + new_defs[k] = true end k = :grid if arg == k - olddefs[k] = default(k) - newdefs[k] = true + old_defs[k] = default(k) + new_defs[k] = true end end # now set all those defaults - default(; newdefs...) + default(; new_defs...) scalefonts ≡ nothing || scalefontsizes(scalefonts) # call the function - ret = f() + ret = Base.invokelatest(f) # put the defaults back scalefonts ≡ nothing || resetfontsizes() - default(; olddefs...) + default(; old_defs...) # revert the backend - CURRENT_BACKEND.sym != oldbackend && backend(oldbackend) + old_backend != backend_name() && backend(old_backend) # return the result of the function ret end # --------------------------------------------------------------- +const _convert_sci_unicode_dict = Dict( + '⁰' => "0", + '¹' => "1", + '²' => "2", + '³' => "3", + '⁴' => "4", + '⁵' => "5", + '⁶' => "6", + '⁷' => "7", + '⁸' => "8", + '⁹' => "9", + '⁻' => "-", + "×10" => "×10^{", +) # converts unicode scientific notation, as returned by Showoff, # to a tex-like format (supported by gr, pyplot, and pgfplots). function convert_sci_unicode(label::AbstractString) - unicode_dict = Dict( - '⁰' => "0", - '¹' => "1", - '²' => "2", - '³' => "3", - '⁴' => "4", - '⁵' => "5", - '⁶' => "6", - '⁷' => "7", - '⁸' => "8", - '⁹' => "9", - '⁻' => "-", - "×10" => "×10^{", - ) - for key in keys(unicode_dict) - label = replace(label, key => unicode_dict[key]) + for key in keys(_convert_sci_unicode_dict) + label = replace(label, key => _convert_sci_unicode_dict[key]) end if occursin("×10^{", label) label = string(label, "}") @@ -947,7 +929,7 @@ Computes the distances of the plot limits to a sample of points at the extremes the ranges, and places the legend at the corner where the maximum distance to the limits is found. """ function _guess_best_legend_position(lp::Symbol, plt) - lp === :best || return lp + lp ≡ :best || return lp _guess_best_legend_position(xlims(plt), ylims(plt), plt) end diff --git a/PlotsBase/src/backends/web.jl b/PlotsBase/src/web.jl similarity index 94% rename from PlotsBase/src/backends/web.jl rename to PlotsBase/src/web.jl index cfa7e454c..1dc382c06 100644 --- a/PlotsBase/src/backends/web.jl +++ b/PlotsBase/src/web.jl @@ -1,5 +1,5 @@ -# NOTE: backend should implement `html_body` and `html_head` +# NOTE: backend should implement `html_body` and `html_head` # CREDIT: parts of this implementation were inspired by @joshday's PlotlyLocal.jl @@ -46,7 +46,7 @@ function standalone_html_window(plt::AbstractPlot) # if we open a browser ourself, we can host local files, so # when we have a local plotly downloaded this is the way to go! _use_local_dependencies[] = - _plotly_local_file_path[] === nothing ? false : isfile(_plotly_local_file_path[]) + _plotly_local_file_path[] ≡ nothing ? false : isfile(_plotly_local_file_path[]) filename = write_temp_html(plt) open_browser_window(filename) # restore for other backends diff --git a/PlotsBase/test/runtests.jl b/PlotsBase/test/runtests.jl index 2d8048ad1..a6f90621d 100644 --- a/PlotsBase/test/runtests.jl +++ b/PlotsBase/test/runtests.jl @@ -1,24 +1,28 @@ const TEST_PACKAGES = - strip.( - split( - get( - ENV, - "PLOTSBASE_TEST_PACKAGES", - "GR,UnicodePlots,PythonPlot,PGFPlotsX,PlotlyJS,Gaston", - ), - ",", + let val = get( + ENV, + "PLOTSBASE_TEST_PACKAGES", + "GR,UnicodePlots,PythonPlot,PGFPlotsX,PlotlyJS,Gaston", ) - ) -const TEST_BACKENDS = Symbol.(lowercase.(TEST_PACKAGES)) + Symbol.(strip.(split(val, ","))) + end +const TEST_BACKENDS = NamedTuple(p => Symbol(lowercase(string(p))) for p in TEST_PACKAGES) + +get!(ENV, "MPLBACKEND", "agg") using PlotsBase +# always initialize GR +import GR +gr() + # initialize all backends for pkg in TEST_PACKAGES - @eval import $(Symbol(pkg)) # trigger extension - getproperty(PlotsBase, Symbol(lowercase(pkg)))() + @eval begin + import $pkg # trigger extension + $(TEST_BACKENDS[pkg])() + end end -gr() import Unitful: m, s, cm, DimensionError import PlotsBase: PLOTS_SEED, Plot, with @@ -35,6 +39,7 @@ using RecipesPipeline using FilePathsBase using LaTeXStrings using RecipesBase +using Preferences using TestImages using Unitful using FileIO @@ -47,8 +52,19 @@ is_ci() = PlotsBase.bool_env("CI") is_ci() || @eval using Gtk # see JuliaPlots/VisualRegressionTests.jl/issues/30 +ref_name(i) = "ref" * lpad(i, 3, '0') + +const blacklist = if VERSION.major == 1 && VERSION.minor ≥ 9 + [ + 25, + 30, # FIXME: remove, when StatsPlots supports Plots v2 + 41, + ] # FIXME: github.com/JuliaLang/julia/issues/47261 +else + [] +end + for name in ( - "quality", "misc", "utils", "args", @@ -61,12 +77,15 @@ for name in ( "shorthands", "recipes", "unitful", - "hdf5plots", # broken ? + "hdf5plots", "pgfplotsx", "plotly", "animations", "output", + "reference", "backends", + "preferences", + "quality", ) @testset "$name" begin # skip the majority of tests if we only want to update reference images or under `PkgEval` (timeout limit) diff --git a/PlotsBase/test/test_args.jl b/PlotsBase/test/test_args.jl index 1e0da0f8e..a1c4207ee 100644 --- a/PlotsBase/test/test_args.jl +++ b/PlotsBase/test/test_args.jl @@ -16,14 +16,14 @@ x = collect(0.0:10.0) foo = Foo(x, sin.(x)) @testset "Magic attributes" begin - @test plot(foo)[1][1][:markershape] === :+ - @test plot(foo, markershape = :diamond)[1][1][:markershape] === :diamond - @test plot(foo, marker = :diamond)[1][1][:markershape] === :diamond + @test plot(foo)[1][1][:markershape] ≡ :+ + @test plot(foo, markershape = :diamond)[1][1][:markershape] ≡ :diamond + @test plot(foo, marker = :diamond)[1][1][:markershape] ≡ :diamond @test (@test_logs (:warn, "Skipped marker arg diamond.") plot( foo, marker = :diamond, markershape = :diamond, - )[1][1][:markershape]) === :diamond + )[1][1][:markershape]) ≡ :diamond end @testset "Subplot Attributes" begin diff --git a/PlotsBase/test/test_axes.jl b/PlotsBase/test/test_axes.jl index abda149df..9a9bba0f2 100644 --- a/PlotsBase/test/test_axes.jl +++ b/PlotsBase/test/test_axes.jl @@ -21,13 +21,15 @@ @test PlotsBase.labelfunc_tex(:log2)(1) == "2^{1}" @test PlotsBase.labelfunc_tex(:ln)(1) == "e^{1}" - @test PlotsBase.get_labels(:auto, 1:3, :identity) == ["1", "2", "3"] - @test PlotsBase.get_labels(:scientific, float.(500:500:1500), :identity) == - ["5.00×10^{2}", "1.00×10^{3}", "1.50×10^{3}"] - @test PlotsBase.get_labels(:engineering, float.(500:500:1500), :identity) == - ["500.×10^{0}", "1.00×10^{3}", "1.50×10^{3}"] - @test PlotsBase.get_labels(:latex, 1:3, :identity) == ["\$1\$", "\$2\$", "\$3\$"] - # GR is used during tests and it correctly overrides labelfunc(), but PGFPlotsX did not + with(:gr) do # NOTE: GR overrides `labelfunc` + @test PlotsBase.get_labels(:auto, 1:3, :identity) == ["1", "2", "3"] + @test PlotsBase.get_labels(:scientific, float.(500:500:1500), :identity) == + ["5.00×10^{2}", "1.00×10^{3}", "1.50×10^{3}"] + @test PlotsBase.get_labels(:engineering, float.(500:500:1500), :identity) == + ["500.×10^{0}", "1.00×10^{3}", "1.50×10^{3}"] + @test PlotsBase.get_labels(:latex, 1:3, :identity) == ["\$1\$", "\$2\$", "\$3\$"] + end + # GR is used during tests and it correctly overrides `labelfunc`, but PGFPlotsX did not with(:pgfplotsx) do @test PlotsBase.get_labels(:auto, 1:3, :log10) == ["10^{1}", "10^{2}", "10^{3}"] end @@ -155,9 +157,9 @@ end @test haskey(PlotsBase.Commons._keyAliases, :x_guide_position) @test !haskey(PlotsBase.Commons._keyAliases, :xguide_position) pl = plot(1:2, xl = "x label") - @test pl[1][:xaxis][:guide] === "x label" + @test pl[1][:xaxis][:guide] ≡ "x label" pl = plot(1:2, xrange = (0, 3)) - @test xlims(pl) === (0, 3) + @test xlims(pl) ≡ (0, 3) pl = plot(1:2, xtick = [1.25, 1.5, 1.75]) @test pl[1][:xaxis][:ticks] == [1.25, 1.5, 1.75] pl = plot(1:2, xlabelfontsize = 4) @@ -165,17 +167,17 @@ end pl = plot(1:2, xgα = 0.07) @test pl[1][:xaxis][:gridalpha] ≈ 0.07 pl = plot(1:2, xgridls = :dashdot) - @test pl[1][:xaxis][:gridstyle] === :dashdot + @test pl[1][:xaxis][:gridstyle] ≡ :dashdot pl = plot(1:2, xgridcolor = :red) - @test pl[1][:xaxis][:foreground_color_grid] === RGBA{Float64}(1.0, 0.0, 0.0, 1.0) + @test pl[1][:xaxis][:foreground_color_grid] ≡ RGBA{Float64}(1.0, 0.0, 0.0, 1.0) pl = plot(1:2, xminorgridcolor = :red) - @test pl[1][:xaxis][:foreground_color_minor_grid] === RGBA{Float64}(1.0, 0.0, 0.0, 1.0) + @test pl[1][:xaxis][:foreground_color_minor_grid] ≡ RGBA{Float64}(1.0, 0.0, 0.0, 1.0) pl = plot(1:2, xgrid_lw = 0.01) @test pl[1][:xaxis][:gridlinewidth] ≈ 0.01 pl = plot(1:2, xminorgrid_lw = 0.01) @test pl[1][:xaxis][:minorgridlinewidth] ≈ 0.01 pl = plot(1:2, xtickor = :out) - @test pl[1][:xaxis][:tick_direction] === :out + @test pl[1][:xaxis][:tick_direction] ≡ :out end @testset "Aliases" begin @@ -186,7 +188,7 @@ end pl = plot(1:2, label = "test") @test compare(pl, :guide, "", ===) pl = plot(1:2, lim = (0, 3)) - @test xlims(pl) === ylims(pl) === zlims(pl) === (0, 3) + @test xlims(pl) ≡ ylims(pl) ≡ zlims(pl) ≡ (0, 3) pl = plot(1:2, tick = [1.25, 1.5, 1.75]) @test compare(pl, :ticks, [1.25, 1.5, 1.75], ==) pl = plot(1:2, labelfontsize = 4) @@ -218,7 +220,7 @@ end let pl = plot(1:2) xl, yl = xlims(pl), ylims(pl) - PlotsBase.PlotsPlots.scale_lims!(pl, 1.1) + PlotsBase.scale_lims!(pl, 1.1) @test first(xlims(pl)) < first(xl) @test last(xlims(pl)) > last(xl) @test first(ylims(pl)) < first(yl) @@ -237,7 +239,7 @@ end @testset "no labels" begin # github.com/JuliaPlots/Plots.jl/issues/4475 pl = plot(100:100:300, hcat([1, 2, 4], [-1, -2, -4]); yformatter = :none) - @test pl[1][:yaxis][:formatter] === :none + @test pl[1][:yaxis][:formatter] ≡ :none end @testset "minor ticks" begin @@ -245,9 +247,9 @@ end for minor_intervals in (:auto, :none, nothing, false, true, 0, 1, 2, 3, 4, 5) n_minor_ticks_per_major = if minor_intervals isa Bool minor_intervals ? PlotsBase.Ticks.DEFAULT_MINOR_INTERVALS[] - 1 : 0 - elseif minor_intervals === :auto + elseif minor_intervals ≡ :auto PlotsBase.Ticks.DEFAULT_MINOR_INTERVALS[] - 1 - elseif minor_intervals === :none || minor_intervals isa Nothing + elseif minor_intervals ≡ :none || minor_intervals isa Nothing 0 else max(0, minor_intervals - 1) @@ -265,9 +267,9 @@ end @test minor_ticks isa Nothing 0 end - elseif minor_intervals === :auto + elseif minor_intervals ≡ :auto length(minor_ticks) - elseif minor_intervals === :none || minor_intervals isa Nothing + elseif minor_intervals ≡ :none || minor_intervals isa Nothing @test minor_ticks isa Nothing 0 else diff --git a/PlotsBase/test/test_backends.jl b/PlotsBase/test/test_backends.jl index b30111944..fc5a4f157 100644 --- a/PlotsBase/test/test_backends.jl +++ b/PlotsBase/test/test_backends.jl @@ -1,143 +1,6 @@ -ci_tol() = - if Sys.islinux() - is_pkgeval() ? "1e-2" : "5e-4" - elseif Sys.isapple() - "1e-3" - else - "1e-1" - end - -const TESTS_MODULE = Module(:PlotsBaseTestModule) -const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? ci_tol() : "1e-5")) - -Base.eval(TESTS_MODULE, :(using Random, StableRNGs, PlotsBase)) - -reference_dir(args...) = - if (ref_dir = get(ENV, "PLOTS_REFERENCE_DIR", nothing)) !== nothing - ref_dir - else - joinpath(homedir(), ".julia", "dev", "PlotReferenceImages.jl", args...) - end -reference_path(backend, version) = reference_dir("Plots", string(backend), string(version)) - -function checkout_reference_dir(dn::AbstractString) - mkpath(dn) - local repo - for i in 1:6 - try - repo = LibGit2.clone( - "https://github.com/JuliaPlots/PlotReferenceImages.jl.git", - dn, - ) - break - catch err - @warn err - sleep(20i) - end - end - if (ver = PlotsBase._current_plots_version).prerelease |> isempty - try - tag = LibGit2.GitObject(repo, "v$ver") - hash = string(LibGit2.target(tag)) - LibGit2.checkout!(repo, hash) - catch err - @warn err - end - end - LibGit2.peel(LibGit2.head(repo)) |> println # print some information - nothing -end - -let dn = reference_dir() - isdir(dn) || checkout_reference_dir(dn) -end - -ref_name(i) = "ref" * lpad(i, 3, '0') - -function reference_file(backend, version, i) - # NOTE: keep ref[...].png naming consistent with `PlotDocs` - refdir = reference_dir("Plots", string(backend)) - fn = ref_name(i) * ".png" - reffn = joinpath(refdir, string(version), fn) - for ver in sort(VersionNumber.(readdir(refdir)), rev = true) - if (tmpfn = joinpath(refdir, string(ver), fn)) |> isfile - reffn = tmpfn - break - end - end - return reffn -end - -function image_comparison_tests( - pkg::Symbol, - idx::Int; - debug = false, - popup = !is_ci(), - sigma = [1, 1], - tol = 1e-2, -) - example = PlotsBase._examples[idx] - @info "Testing plot: $pkg:$idx:$(example.header)" - - ver = PlotsBase._current_plots_version - ver = VersionNumber(ver.major, ver.minor, ver.patch) - reffn = reference_file(pkg, ver, idx) - newfn = joinpath(reference_path(pkg, ver), ref_name(idx) * ".png") - - imports = something(example.imports, :()) - exprs = quote - PlotsBase.Commons.debug!($debug) - backend($(QuoteNode(pkg))) - theme(:default) - rng = StableRNG(PlotsBase.PLOTS_SEED) - $(PlotsBase.replace_rand(example.exprs)) - end - @debug imports exprs - - func = fn -> Base.eval.(Ref(TESTS_MODULE), (imports, exprs, :(png($fn)))) - test_images( - VisualTest(func, reffn), - newfn = newfn, - popup = popup, - sigma = sigma, - tol = tol, - ) -end - -function image_comparison_facts( - pkg::Symbol; - skip = [], # skip these examples (int index) - only = nothing, # limit to these examples (int index) - debug = false, # print debug information ? - sigma = [1, 1], # number of pixels to "blur" - tol = 1e-2, # acceptable error (percent) -) - for i in setdiff(1:length(PlotsBase._examples), skip) - if only === nothing || i in only - @test success(image_comparison_tests(pkg, i; debug, sigma, tol)) - end - end -end - -## Uncomment the following lines to update reference images for different backends -#= - -with(:gr) do - image_comparison_facts(:gr, tol = PLOTS_IMG_TOL, skip = PlotsBase._backend_skips[:gr]) -end - -with(:plotlyjs) do - image_comparison_facts(:plotlyjs, tol = PLOTS_IMG_TOL, skip = PlotsBase._backend_skips[:plotlyjs]) -end - -with(:pgfplotsx) do - image_comparison_facts(:pgfplotsx, tol = PLOTS_IMG_TOL, skip = PlotsBase._backend_skips[:pgfplotsx]) -end -=# - @testset "UnicodePlots" begin with(:unicodeplots) do - @test backend() == PlotsBase._backend_instance(:unicodeplots) + @test backend() == PlotsBase.backend_instance(:unicodeplots) io = IOContext(IOBuffer(), :color => true) @@ -181,43 +44,20 @@ end end end -const blacklist = if VERSION.major == 1 && VERSION.minor ∈ (9, 10) - [ - 25, - 30, # FIXME: remove, when StatsPlots supports Plots v2 - 41, - ] # FIXME: github.com/JuliaLang/julia/issues/47261 -else - [] -end - -@testset "GR - reference images" begin - with(:gr) do - # NOTE: use `ENV["VISUAL_REGRESSION_TESTS_AUTO"] = true;` to automatically replace reference images - @test backend() == PlotsBase._backend_instance(:gr) - @test backend_name() === :gr - image_comparison_facts( - :gr, - tol = PLOTS_IMG_TOL, - skip = vcat(PlotsBase._backend_skips[:gr], blacklist), - ) - end -end - -is_pkgeval() || @testset "PlotlyJS" begin +(is_pkgeval() || is_ci()) || @testset "PlotlyJS" begin with(:plotlyjs) do PlotlyJSExt = Base.get_extension(PlotsBase, :PlotlyJSExt) @test backend() == PlotlyJSExt.PlotlyJSBackend() pl = plot(rand(10)) @test pl isa Plot - @test display(pl) isa Nothing + display(pl) end end -is_pkgeval() || @testset "Examples" begin +is_pkgeval() || @testset "Backends" begin callback(m, pkgname, i) = begin - pl = m.PlotsBase.current() save_func = (; pgfplotsx = m.PlotsBase.pdf, unicodeplots = m.PlotsBase.txt) # fastest `savefig` for each backend + pl = m.PlotsBase.current() fn = Base.invokelatest( get(save_func, pkgname, m.PlotsBase.png), pl, diff --git a/PlotsBase/test/test_components.jl b/PlotsBase/test/test_components.jl index 501c43bab..62a84c6ac 100644 --- a/PlotsBase/test/test_components.jl +++ b/PlotsBase/test/test_components.jl @@ -55,7 +55,7 @@ @test square2.y ≈ coordsRotated2[2, :] # unrotate the new square in place - rotate!(square2, 2) + PlotsBase.rotate!(square2, 2) @test square2.x ≈ coords[1, :] @test square2.y ≈ coords[2, :] end @@ -67,7 +67,7 @@ @test coords(myshapes) isa Tuple{Vector{Vector{S}},Vector{Vector{T}}} where {T,S} local pl @test_nowarn pl = plot(myshapes) - @test pl[1][1][:seriestype] === :shape + @test pl[1][1][:seriestype] ≡ :shape end @testset "Misc" begin @@ -102,7 +102,7 @@ end end @testset "Alpha" begin @test brush(0.4).alpha == 0.4 - @test brush(20).alpha === nothing + @test brush(20).alpha ≡ nothing end @testset "Bad Argument" begin # using test_logs because test_warn seems to not work anymore @@ -138,7 +138,7 @@ end @test PlotsBase.series_annotations(["1" "2"; "3" "4"]) isa AbstractMatrix @test PlotsBase.series_annotations(10).strs[1].str == "10" - @test PlotsBase.series_annotations(nothing) === nothing + @test PlotsBase.series_annotations(nothing) ≡ nothing @test PlotsBase.series_annotations(ann) == ann @test PlotsBase.annotations(["1" "2"; "3" "4"]) isa AbstractMatrix diff --git a/PlotsBase/test/test_contours.jl b/PlotsBase/test/test_contours.jl index 5ec0072c8..04c656c00 100644 --- a/PlotsBase/test/test_contours.jl +++ b/PlotsBase/test/test_contours.jl @@ -1,8 +1,8 @@ @testset "check_contour_levels" begin let check_contour_levels = PlotsBase.Commons.check_contour_levels - @test check_contour_levels(2) === nothing - @test check_contour_levels(-1.0:0.2:10.0) === nothing - @test check_contour_levels([-100, -2, -1, 0, 1, 2, 100]) === nothing + @test check_contour_levels(2) ≡ nothing + @test check_contour_levels(-1.0:0.2:10.0) ≡ nothing + @test check_contour_levels([-100, -2, -1, 0, 1, 2, 100]) ≡ nothing @test_throws ArgumentError check_contour_levels(1.0) @test_throws ArgumentError check_contour_levels((1, 2, 3)) @test_throws ArgumentError check_contour_levels(-3) @@ -46,7 +46,7 @@ end @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[:seriestype] ≡ :contour @test attr[:levels] == n end end diff --git a/PlotsBase/test/test_defaults.jl b/PlotsBase/test/test_defaults.jl index f75fff7af..94cf69614 100644 --- a/PlotsBase/test/test_defaults.jl +++ b/PlotsBase/test/test_defaults.jl @@ -25,16 +25,16 @@ end pl = plot() @test pl[1][:legend_font_family] == "sans-serif" @test pl[1][:legend_font_pointsize] == 8 - @test pl[1][:legend_font_halign] === :hcenter - @test pl[1][:legend_font_valign] === :vcenter + @test pl[1][:legend_font_halign] ≡ :hcenter + @test pl[1][:legend_font_valign] ≡ :vcenter @test pl[1][:legend_font_rotation] == 0.0 @test pl[1][:legend_font_color] == RGB{Colors.N0f8}(0.0, 0.0, 0.0) - @test pl[1][:legend_position] === :best - @test pl[1][:legend_title] === nothing + @test pl[1][:legend_position] ≡ :best + @test pl[1][:legend_title] ≡ nothing @test pl[1][:legend_title_font_family] == "sans-serif" @test pl[1][:legend_title_font_pointsize] == 11 - @test pl[1][:legend_title_font_halign] === :hcenter - @test pl[1][:legend_title_font_valign] === :vcenter + @test pl[1][:legend_title_font_halign] ≡ :hcenter + @test pl[1][:legend_title_font_valign] ≡ :vcenter @test pl[1][:legend_title_font_rotation] == 0.0 @test pl[1][:legend_title_font_color] == RGB{Colors.N0f8}(0.0, 0.0, 0.0) @test pl[1][:legend_background_color] == RGBA{Float64}(1.0, 1.0, 1.0, 1.0) @@ -62,18 +62,18 @@ end ) @test pl[1][:legend_font_family] == "serif" @test pl[1][:legend_font_pointsize] == 12 - @test pl[1][:legend_font_halign] === :left - @test pl[1][:legend_font_valign] === :top + @test pl[1][:legend_font_halign] ≡ :left + @test pl[1][:legend_font_valign] ≡ :top @test pl[1][:legend_font_rotation] == 1.0 - @test pl[1][:legend_font_color] === :red - @test pl[1][:legend_position] === :outertopleft + @test pl[1][:legend_font_color] ≡ :red + @test pl[1][:legend_position] ≡ :outertopleft @test pl[1][:legend_title] == "The legend" @test pl[1][:legend_title_font_family] == "helvetica" @test pl[1][:legend_title_font_pointsize] == 3 - @test pl[1][:legend_title_font_halign] === :right - @test pl[1][:legend_title_font_valign] === :bottom + @test pl[1][:legend_title_font_halign] ≡ :right + @test pl[1][:legend_title_font_valign] ≡ :bottom @test pl[1][:legend_title_font_rotation] == -5.2 - @test pl[1][:legend_title_font_color] === :blue + @test pl[1][:legend_title_font_color] ≡ :blue @test pl[1][:legend_background_color] == RGBA{Float64}(0.0, 1.0, 1.0, 1.0) @test pl[1][:legend_foreground_color] == RGBA{Float64}(0.0, 0.5019607843137255, 0.0, 1.0) @@ -91,7 +91,7 @@ end foreground_color_subplot = :red, )[1] @test PlotsBase.legendfont(sp).pointsize == 12 - @test PlotsBase.legendfont(sp).halign === :left + @test PlotsBase.legendfont(sp).halign ≡ :left # match mechanism @test sp[:legend_font_color] == colorant"black" @test PlotsBase.legendfont(sp).color == colorant"black" diff --git a/PlotsBase/test/test_layouts.jl b/PlotsBase/test/test_layouts.jl index b98df9b4e..90cf6f4a1 100644 --- a/PlotsBase/test/test_layouts.jl +++ b/PlotsBase/test/test_layouts.jl @@ -11,10 +11,10 @@ end layout = 4, yscale = [:identity :identity :log10 :log10], ) - @test pl[1][:yaxis][:scale] === :identity - @test pl[2][:yaxis][:scale] === :identity - @test pl[3][:yaxis][:scale] === :log10 - @test pl[4][:yaxis][:scale] === :log10 + @test pl[1][:yaxis][:scale] ≡ :identity + @test pl[2][:yaxis][:scale] ≡ :identity + @test pl[3][:yaxis][:scale] ≡ :log10 + @test pl[4][:yaxis][:scale] ≡ :log10 end @testset "Plot title" begin @@ -41,9 +41,9 @@ end @testset "Plots.jl/issues/4083" begin pl = plot(plot(1:2), plot(1:2); border = :grid, plot_title = "abc") - @test pl[1][:framestyle] === :grid - @test pl[2][:framestyle] === :grid - @test pl[3][:framestyle] === :none + @test pl[1][:framestyle] ≡ :grid + @test pl[2][:framestyle] ≡ :grid + @test pl[3][:framestyle] ≡ :none end @testset "Allowed subplot counts" begin @@ -108,16 +108,16 @@ end show(io, PlotsBase.DEFAULT_BBOX[]) show(io, pl.layout) - @test PlotsBase.make_measure_hor(1PlotsBase.mm) == 1PlotsBase.mm - @test PlotsBase.make_measure_vert(1PlotsBase.mm) == 1PlotsBase.mm + @test PlotsBase.Commons.make_measure_hor(1PlotsBase.mm) == 1PlotsBase.mm + @test PlotsBase.Commons.make_measure_vert(1PlotsBase.mm) == 1PlotsBase.mm @test PlotsBase.parent(pl.layout) isa PlotsBase.RootLayout - show(io, PlotsBase.parent_bbox(pl.layout)) + show(io, PlotsBase.Commons.parent_bbox(pl.layout)) rl = PlotsBase.RootLayout() show(io, rl) - @test parent(rl) === nothing - @test PlotsBase.parent_bbox(rl) == PlotsBase.DEFAULT_BBOX[] + @test parent(rl) ≡ nothing + @test PlotsBase.Commons.parent_bbox(rl) == PlotsBase.DEFAULT_BBOX[] @test PlotsBase.bbox(rl) == PlotsBase.DEFAULT_BBOX[] @test PlotsBase.origin(PlotsBase.DEFAULT_BBOX[]) == (0PlotsBase.mm, 0PlotsBase.mm) for h_anchor in (:left, :right, :hcenter), v_anchor in (:top, :bottom, :vcenter) @@ -125,10 +125,10 @@ end end el = PlotsBase.EmptyLayout() - @test PlotsBase.update_position!(el) === nothing + @test PlotsBase.update_position!(el) ≡ nothing @test size(el) == (0, 0) @test length(el) == 0 - @test el[1, 1] === nothing + @test el[1, 1] ≡ nothing @test PlotsBase.left(el) == 0PlotsBase.mm @test PlotsBase.top(el) == 0PlotsBase.mm diff --git a/PlotsBase/test/test_misc.jl b/PlotsBase/test/test_misc.jl index 6a53aad68..864082dad 100644 --- a/PlotsBase/test/test_misc.jl +++ b/PlotsBase/test/test_misc.jl @@ -21,7 +21,7 @@ end @testset "NoFail" begin with(:unicodeplots) do - @test backend() == PlotsBase._backend_instance(:unicodeplots) + @test backend() == PlotsBase.backend_instance(:unicodeplots) dsp = TextDisplay(IOContext(IOBuffer(), :color => true)) @@ -124,7 +124,7 @@ end value(m::MyType) = m.val data = MyType.(sort(randn(20))) - # A recipe that puts the axis letter in the title + # a recipe that puts the axis letter in the title @recipe function f(::Type{T}, m::T) where {T<:AbstractArray{<:MyType}} title --> string(plotattributes[:letter]) value.(m) diff --git a/PlotsBase/test/test_output.jl b/PlotsBase/test/test_output.jl index b929e9845..e221c1819 100644 --- a/PlotsBase/test/test_output.jl +++ b/PlotsBase/test/test_output.jl @@ -81,7 +81,7 @@ if Sys.islinux() && Sys.which("pdflatex") ≢ nothing end end -with(:gaston) do +Sys.islinux() && with(:gaston) do @test_save :png @test_save :pdf @test_save :eps diff --git a/PlotsBase/test/test_pgfplotsx.jl b/PlotsBase/test/test_pgfplotsx.jl index 81dde6ea1..07bacbedf 100644 --- a/PlotsBase/test/test_pgfplotsx.jl +++ b/PlotsBase/test/test_pgfplotsx.jl @@ -20,7 +20,7 @@ with(:pgfplotsx) do pl = plot(1:5) axis = first(get_pgf_axes(pl)) @test pl.o.the_plot isa PGFPlotsX.TikzDocument - @test pl.series_list[1].plotattributes[:quiver] === nothing + @test pl.series_list[1].plotattributes[:quiver] ≡ nothing @test count(x -> x isa PGFPlotsX.Plot, axis.contents) == 1 @test !haskey(axis.contents[1].options.dict, "fill") @test occursin("documentclass", PlotsBase.pgfx_preamble(pl)) @@ -60,7 +60,7 @@ with(:pgfplotsx) do pl = plot!(pl, zeros(n), zeros(n), 1:n, w = 10) axis = first(get_pgf_axes(pl)) if @test_nowarn(haskey(axis.options.dict, "colorbar")) - @test axis["colorbar"] === nothing + @test axis["colorbar"] ≡ nothing end end @@ -190,7 +190,7 @@ with(:pgfplotsx) do pl = heatmap(xs, ys, z, aspect_ratio = 1) axis = first(get_pgf_axes(pl)) if @test_nowarn(haskey(axis.options.dict, "colorbar")) - @test axis["colorbar"] === nothing + @test axis["colorbar"] ≡ nothing @test axis["colormap name"] == "plots1" end @@ -336,8 +336,8 @@ with(:pgfplotsx) do @test haskey(plots[1].options.dict, "fill") @test haskey(plots[2].options.dict, "fill") @test !haskey(plots[3].options.dict, "fill") - @test pl.o !== nothing - @test pl.o.the_plot !== nothing + @test pl.o ≢ nothing + @test pl.o.the_plot ≢ nothing end @testset "Markers and Paths" begin @@ -414,14 +414,14 @@ with(:pgfplotsx) do pl = plot(1:5, title = "Test me", titlefont = (2, :left)) @test pl[1][:title] == "Test me" @test pl[1][:titlefontsize] == 2 - @test pl[1][:titlefonthalign] === :left + @test pl[1][:titlefonthalign] ≡ :left ax_opt = first(get_pgf_axes(pl)).options @test ax_opt["title"] == "Test me" @test(haskey(ax_opt.dict, "title style")) isa Test.Pass pl = plot(1:5, plot_title = "Test me", plot_titlefont = (2, :left)) @test pl[:plot_title] == "Test me" @test pl[:plot_titlefontsize] == 2 - @test pl[:plot_titlefonthalign] === :left + @test pl[:plot_titlefonthalign] ≡ :left pl = heatmap( rand(3, 3), colorbar_title = "Test me", @@ -429,7 +429,7 @@ with(:pgfplotsx) do ) @test pl[1][:colorbar_title] == "Test me" @test pl[1][:colorbar_titlefontsize] == 12 - @test pl[1][:colorbar_titlefonthalign] === :right + @test pl[1][:colorbar_titlefonthalign] ≡ :right end @testset "Latexify - LaTeXStrings" begin @@ -455,7 +455,7 @@ with(:pgfplotsx) do plt1 = plot(rand(10, 5)) plt2 = plot(rand(10)) - @test plot(plt1, plt2, layout = (1, 2), plot_titles = ["(a)" "(b)"]) !== nothing + @test plot(plt1, plt2, layout = (1, 2), plot_titles = ["(a)" "(b)"]) ≢ nothing end if Sys.islinux() && Sys.which("pdflatex") ≢ nothing diff --git a/PlotsBase/test/test_preferences.jl b/PlotsBase/test/test_preferences.jl new file mode 100644 index 000000000..ae6cac8b7 --- /dev/null +++ b/PlotsBase/test/test_preferences.jl @@ -0,0 +1,96 @@ +# get `Preferences` set backend, if any +const PREVIOUS_DEFAULT_BACKEND = load_preference(PlotsBase, "default_backend") +# ----------------------------------------------------------------------------- + +PlotsBase.set_default_backend!() # start with empty preferences + +withenv("PLOTSBASE_DEFAULT_BACKEND" => "test_invalid_backend") do + @test_logs (:error, r"Unsupported backend.*") PlotsBase.default_backend() +end +@test_logs (:error, r"Unsupported backend.*") backend(:test_invalid_backend) + +@test PlotsBase.default_backend() == Base.get_extension(PlotsBase, :GRExt).GRBackend() + +withenv("PLOTSBASE_DEFAULT_BACKEND" => "unicodeplots") do + @test_logs (:info, r".*environment variable") PlotsBase.diagnostics(devnull) + @test PlotsBase.default_backend() == + Base.get_extension(PlotsBase, :UnicodePlotsExt).UnicodePlotsBackend() +end + +@test PlotsBase.default_backend() == Base.get_extension(PlotsBase, :GRExt).GRBackend() +@test PlotsBase.backend_package_name() ≡ :GR +@test PlotsBase.backend_name() ≡ :gr + +@test_logs (:info, r".*fallback") PlotsBase.diagnostics(devnull) + +@test PlotsBase.merge_with_base_supported([:annotations, :guide]) isa Set +@test PlotsBase.CurrentBackend(:gr).name ≡ :gr + +@test_logs (:warn, r".*is not compatible with") PlotsBase.set_default_backend!( + :test_invalid_backend, +) + +const DEBUG = false +@testset "persistent backend - restart" begin + # this test mimics a restart, which is needed after a preferences change + PlotsBase.set_default_backend!(:unicodeplots) + script = tempname() + dn = pkgdir(PlotsBase) |> escape_string + write( + script, + """ + using Pkg, Test; io = (devnull, stdout)[1] # toggle for debugging + Pkg.activate(; temp = true, io) + Pkg.develop(; path = joinpath("$dn", "..", "RecipesBase"), io) + Pkg.develop(; path = joinpath("$dn", "..", "RecipesPipeline"), io) + Pkg.develop(; path = "$dn", io) + Pkg.add("UnicodePlots"; io) # checked by Plots + import UnicodePlots + using PlotsBase + unicodeplots() + res = @testset "[subtest] preferences UnicodePlots" begin + @test_logs (:info, r".*Preferences") PlotsBase.diagnostics(io) + @test backend() == Base.get_extension(PlotsBase, :UnicodePlotsExt).UnicodePlotsBackend() + end + exit(res.n_passed == 2 ? 0 : 123) + """, + ) + DEBUG && print(read(script, String)) + @test run(```$(Base.julia_cmd()) $script```) |> success +end + +is_pkgeval() || for pkg in TEST_PACKAGES + @testset "persistent backend $pkg" begin + be = TEST_BACKENDS[pkg] + if is_ci() + (Sys.isapple() && be ≡ :gaston) && continue # FIXME: hangs + (Sys.iswindows() && be ≡ :plotlyjs) && continue # FIXME: OutOfMemory + end + @test_logs PlotsBase.set_default_backend!(be) # test the absence of warnings + rm.(Base.find_all_in_cache_path(Base.module_keys[PlotsBase])) # make sure the compiled cache is removed + script = tempname() + write( + script, + """ + import $pkg + using Test, PlotsBase + $be() + res = @testset "[subtest] persistent backend $pkg" begin + @test PlotsBase.backend_name() ≡ :$be + end + exit(res.n_passed == 1 ? 0 : 123) + """, + ) + DEBUG && print(read(script, String)) + @test run(```$(Base.julia_cmd()) $script```) |> success # test default precompilation + end +end + +PlotsBase.set_default_backend!() # clear `Preferences` key + +# ----------------------------------------------------------------------------- +if PREVIOUS_DEFAULT_BACKEND ≡ nothing + delete_preferences!(PlotsBase, "default_backend") # restore the absence of a preference +else + set_default_backend!(PREVIOUS_DEFAULT_BACKEND) # reset to previous state +end diff --git a/PlotsBase/test/test_quality.jl b/PlotsBase/test/test_quality.jl index aba35594e..8fccb5a1f 100644 --- a/PlotsBase/test/test_quality.jl +++ b/PlotsBase/test/test_quality.jl @@ -1,20 +1,13 @@ @testset "Auto QUality Assurance" begin # JuliaTesting/Aqua.jl/issues/77 # TODO: fix :Contour, :Latexify and :LaTeXStrings stale imports in Plots 2.0 - # :PyCall and :Conda stale deps show up when running CI + # :CondaPkg stale deps show up when running CI Aqua.test_all( PlotsBase; stale_deps = (; - ignore = [ - :GR, - :CondaPkg, - :Contour, - :Latexify, - :LaTeXStrings, - :Requires, - :UnitfulLatexify, - ] + ignore = [:CondaPkg, :Contour, :UnitfulLatexify, :LaTeXStrings, :Latexify] ), + persistent_tasks = false, ambiguities = false, deps_compat = false, # FIXME: fails `CondaPkg` piracies = false, diff --git a/PlotsBase/test/test_recipes.jl b/PlotsBase/test/test_recipes.jl index 86cd0e007..9c4abddde 100644 --- a/PlotsBase/test/test_recipes.jl +++ b/PlotsBase/test/test_recipes.jl @@ -7,16 +7,16 @@ using OffsetArrays (1:3, 1:3) end let pl = pl = plot(LegendPlot(); legend = :right) - @test pl[1][:legend_position] === :right + @test pl[1][:legend_position] ≡ :right end let pl = pl = plot(LegendPlot()) - @test pl[1][:legend_position] === :topleft + @test pl[1][:legend_position] ≡ :topleft end let pl = plot(LegendPlot(); legend = :inline) - @test pl[1][:legend_position] === :inline + @test pl[1][:legend_position] ≡ :inline end let pl = plot(LegendPlot(); legend = :inline, ymirror = true) - @test pl[1][:legend_position] === :inline + @test pl[1][:legend_position] ≡ :inline end end @@ -24,7 +24,7 @@ end pl = plot(1:5) lens!(pl, [1, 2], [1, 2], inset = (1, bbox(0.0, 0.0, 0.2, 0.2)), colorbar = false) @test length(pl.series_list) == 4 - @test pl[2][:colorbar] === :none + @test pl[2][:colorbar] ≡ :none end @testset "vline, vspan" begin @@ -96,10 +96,10 @@ end # TODO: that should cover all seriestypes without the need to have the extension loaded # currently uses plotly seriestypes only @test :surface in PlotsBase.all_seriestypes() - unicode_instance = PlotsBase._backend_instance(:unicodeplots) - @test PlotsBase.seriestype_supported(unicode_instance, :surface) === :native - @test PlotsBase.seriestype_supported(unicode_instance, :hspan) === :recipe - @test PlotsBase.seriestype_supported(PlotsBase.NoBackend(), :line) === :native + unicode_instance = PlotsBase.backend_instance(:unicodeplots) + @test PlotsBase.seriestype_supported(unicode_instance, :surface) ≡ :native + @test PlotsBase.seriestype_supported(unicode_instance, :hspan) ≡ :recipe + @test PlotsBase.seriestype_supported(PlotsBase.NoBackend(), :line) ≡ :native end with(:gr) do diff --git a/PlotsBase/test/test_reference.jl b/PlotsBase/test/test_reference.jl new file mode 100644 index 000000000..203454434 --- /dev/null +++ b/PlotsBase/test/test_reference.jl @@ -0,0 +1,147 @@ +ci_tol() = + if Sys.islinux() + is_pkgeval() ? "1e-2" : "5e-4" + elseif Sys.isapple() + "1e-3" + else + "1e-1" + end + +const TESTS_MODULE = Module(:PlotsBaseTestModule) +const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? ci_tol() : "1e-5")) + +Base.eval(TESTS_MODULE, :(using Random, StableRNGs, PlotsBase)) + +reference_dir(args...) = + if (ref_dir = get(ENV, "PLOTS_REFERENCE_DIR", nothing)) ≢ nothing + ref_dir + else + joinpath(homedir(), ".julia", "dev", "PlotReferenceImages.jl", args...) + end +reference_path(backend, version) = reference_dir("Plots", string(backend), string(version)) + +function checkout_reference_dir(dn::AbstractString) + mkpath(dn) + local repo + for i in 1:6 + try + repo = LibGit2.clone( + "https://github.com/JuliaPlots/PlotReferenceImages.jl.git", + dn, + ) + break + catch err + @warn err + sleep(20i) + end + end + if (ver = PlotsBase._version).prerelease |> isempty + try + tag = LibGit2.GitObject(repo, "v$ver") + hash = string(LibGit2.target(tag)) + LibGit2.checkout!(repo, hash) + catch err + @warn err + end + end + LibGit2.peel(LibGit2.head(repo)) |> println # print some information + nothing +end + +let dn = reference_dir() + isdir(dn) || checkout_reference_dir(dn) +end + +function reference_file(backend, version, i) + # NOTE: keep ref[...].png naming consistent with `PlotDocs` + refdir = reference_dir("Plots", string(backend)) + fn = ref_name(i) * ".png" + reffn = joinpath(refdir, string(version), fn) + for ver in sort(VersionNumber.(readdir(refdir)), rev = true) + if (tmpfn = joinpath(refdir, string(ver), fn)) |> isfile + reffn = tmpfn + break + end + end + return reffn +end + +function image_comparison_tests( + pkg::Symbol, + idx::Int; + debug = false, + popup = !is_ci(), + sigma = [1, 1], + tol = 1e-2, +) + example = PlotsBase._examples[idx] + @info "Testing plot: $pkg:$idx:$(example.header)" + + ver = PlotsBase._version + ver = VersionNumber(ver.major, ver.minor, ver.patch) + reffn = reference_file(pkg, ver, idx) + newfn = joinpath(reference_path(pkg, ver), ref_name(idx) * ".png") + + imports = something(example.imports, :()) + exprs = quote + PlotsBase.Commons.debug!($debug) + backend($(QuoteNode(pkg))) + theme(:default) + rng = StableRNG(PlotsBase.PLOTS_SEED) + $(PlotsBase.replace_rand(example.exprs)) + end + @debug imports exprs + + func = fn -> Base.eval.(Ref(TESTS_MODULE), (imports, exprs, :(png($fn)))) + test_images( + VisualTest(func, reffn), + newfn = newfn, + popup = popup, + sigma = sigma, + tol = tol, + ) +end + +function image_comparison_facts( + pkg::Symbol; + skip = [], # skip these examples (int index) + only = nothing, # limit to these examples (int index) + debug = false, # print debug information ? + sigma = [1, 1], # number of pixels to "blur" + tol = 1e-2, # acceptable error (percent) +) + for i in setdiff(1:length(PlotsBase._examples), skip) + if only ≡ nothing || i in only + @test success(image_comparison_tests(pkg, i; debug, sigma, tol)) + end + end +end + +## Uncomment the following lines to update reference images for different backends +#= + +with(:gr) do + image_comparison_facts(:gr, tol = PLOTS_IMG_TOL, skip = PlotsBase._backend_skips[:gr]) +end + +with(:plotlyjs) do + image_comparison_facts(:plotlyjs, tol = PLOTS_IMG_TOL, skip = PlotsBase._backend_skips[:plotlyjs]) +end + +with(:pgfplotsx) do + image_comparison_facts(:pgfplotsx, tol = PLOTS_IMG_TOL, skip = PlotsBase._backend_skips[:pgfplotsx]) +end +=# + +@testset "GR - reference images" begin + with(:gr) do + # NOTE: use `ENV["VISUAL_REGRESSION_TESTS_AUTO"] = true;` to automatically replace reference images + @test backend() == PlotsBase.backend_instance(:gr) + @test backend_name() ≡ :gr + image_comparison_facts( + :gr, + tol = PLOTS_IMG_TOL, + skip = vcat(PlotsBase._backend_skips[:gr], blacklist), + ) + end +end diff --git a/PlotsBase/test/test_shorthands.jl b/PlotsBase/test/test_shorthands.jl index fb6a96703..69a9cdf28 100644 --- a/PlotsBase/test/test_shorthands.jl +++ b/PlotsBase/test/test_shorthands.jl @@ -97,7 +97,7 @@ end pl = plot3d([1, 2], [1, 2], [1, 2]) plot3d!(pl, [3, 4], [3, 4], [3, 4]) - @test PlotsBase.series_list(pl[1])[1][:seriestype] === :path3d + @test PlotsBase.series_list(pl[1])[1][:seriestype] ≡ :path3d end @testset "Set Ticks" begin diff --git a/PlotsBase/test/test_utils.jl b/PlotsBase/test/test_utils.jl index e3d9ab01b..ea179267a 100644 --- a/PlotsBase/test/test_utils.jl +++ b/PlotsBase/test/test_utils.jl @@ -49,12 +49,12 @@ @test PlotsBase.nansplit([1, 2, NaN, 3, 4]) == [[1.0, 2.0], [3.0, 4.0]] @test PlotsBase.nanvcat([1, NaN]) |> length == 4 - @test PlotsBase.PlotMeasures.inch2px(1) isa AbstractFloat - @test PlotsBase.PlotMeasures.px2inch(1) isa AbstractFloat - @test PlotsBase.PlotMeasures.inch2mm(1) isa AbstractFloat - @test PlotsBase.PlotMeasures.mm2inch(1) isa AbstractFloat - @test PlotsBase.PlotMeasures.px2mm(1) isa AbstractFloat - @test PlotsBase.PlotMeasures.mm2px(1) isa AbstractFloat + @test PlotsBase.Commons.inch2px(1) isa AbstractFloat + @test PlotsBase.Commons.px2inch(1) isa AbstractFloat + @test PlotsBase.Commons.inch2mm(1) isa AbstractFloat + @test PlotsBase.Commons.mm2inch(1) isa AbstractFloat + @test PlotsBase.Commons.px2mm(1) isa AbstractFloat + @test PlotsBase.Commons.mm2px(1) isa AbstractFloat pl = plot() @test xlims() isa Tuple @@ -102,25 +102,25 @@ push!(pl, 1:2, 2:3, 3:4) pl = plot([1, 2, 3], [4, 5, 6]) - @test PlotsBase.PlotsPlots.xmin(pl) == 1 - @test PlotsBase.PlotsPlots.xmax(pl) == 3 + @test PlotsBase.Plots.xmin(pl) == 1 + @test PlotsBase.Plots.xmax(pl) == 3 @test PlotsBase.Commons.ignorenan_extrema(pl) == (1, 3) - @test PlotsBase.Commons.get_attr_symbol(:x, "lims") === :xlims - @test PlotsBase.Commons.get_attr_symbol(:x, :lims) === :xlims + @test PlotsBase.Commons.get_attr_symbol(:x, "lims") ≡ :xlims + @test PlotsBase.Commons.get_attr_symbol(:x, :lims) ≡ :xlims @test contains(PlotsBase._document_argument(:bar_position), "bar_position") - @test PlotsBase.limsType((1, 1)) === :limits - @test PlotsBase.limsType(:undefined) === :invalid - @test PlotsBase.limsType(:auto) === :auto - @test PlotsBase.limsType(NaN) === :invalid + @test PlotsBase.limsType((1, 1)) ≡ :limits + @test PlotsBase.limsType(:undefined) ≡ :invalid + @test PlotsBase.limsType(:auto) ≡ :auto + @test PlotsBase.limsType(NaN) ≡ :invalid - @test PlotsBase.ticks_type([1, 2]) === :ticks - @test PlotsBase.ticks_type(["1", "2"]) === :labels - @test PlotsBase.ticks_type(([1, 2], ["1", "2"])) === :ticks_and_labels - @test PlotsBase.ticks_type(((1, 2), ("1", "2"))) === :ticks_and_labels - @test PlotsBase.ticks_type(:undefined) === :invalid + @test PlotsBase.ticks_type([1, 2]) ≡ :ticks + @test PlotsBase.ticks_type(["1", "2"]) ≡ :labels + @test PlotsBase.ticks_type(([1, 2], ["1", "2"])) ≡ :ticks_and_labels + @test PlotsBase.ticks_type(((1, 2), ("1", "2"))) ≡ :ticks_and_labels + @test PlotsBase.ticks_type(:undefined) ≡ :invalid pl = plot(1:2, 1:2, 1:2, proj_type = :ortho) @test PlotsBase.isortho(first(pl.subplots)) @@ -130,23 +130,23 @@ let pl = plot(1:2) series = first(pl.series_list) label = "fancy label" - PlotsBase.PlotsSeries.attr!(series; label) + PlotsBase.attr!(series; label) @test series[:label] == label - @test PlotsBase.PlotsSeries.attr(series, :label) == label + @test PlotsBase.attr(series, :label) == label label = "another label" - PlotsBase.PlotsSeries.attr!(series, label, :label) - @test PlotsBase.PlotsSeries.attr(series, :label) == label + PlotsBase.attr!(series, label, :label) + @test PlotsBase.attr(series, :label) == label sp = first(pl.subplots) title = "fancy title" - PlotsBase.Subplots.attr!(sp; title) + PlotsBase.attr!(sp; title) @test sp[:title] == title end end @testset "NaN-separated Segments" begin - segments(args...) = collect(PlotsBase.PlotsSeries.iter_segments(args...)) + segments(args...) = collect(PlotsBase.DataSeries.iter_segments(args...)) nan10 = fill(NaN, 10) @test segments(11:20) == [1:10] @@ -195,45 +195,45 @@ end pl = plot(x, x, label = "linear") pl = plot!(x, x .^ 2, label = "quadratic") pl = plot!(x, x .^ 3, label = "cubic") - @test PlotsBase._guess_best_legend_position(:best, pl) === :topleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topleft x = OffsetArrays.OffsetArray(0:0.01:2, OffsetArrays.Origin(-3)) pl = plot(x, x, label = "linear") pl = plot!(x, x .^ 2, label = "quadratic") pl = plot!(x, x .^ 3, label = "cubic") - @test PlotsBase._guess_best_legend_position(:best, pl) === :topleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topleft x = OffsetArrays.OffsetArray(0:0.01:2, OffsetArrays.Origin(+3)) pl = plot(x, x, label = "linear") pl = plot!(x, x .^ 2, label = "quadratic") pl = plot!(x, x .^ 3, label = "cubic") - @test PlotsBase._guess_best_legend_position(:best, pl) === :topleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topleft x = 0:0.01:2 pl = plot(x, -x, label = "linear") pl = plot!(x, -x .^ 2, label = "quadratic") pl = plot!(x, -x .^ 3, label = "cubic") - @test PlotsBase._guess_best_legend_position(:best, pl) === :bottomleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :bottomleft x = OffsetArrays.OffsetArray(0:0.01:2, OffsetArrays.Origin(-3)) pl = plot(x, -x, label = "linear") pl = plot!(x, -x .^ 2, label = "quadratic") pl = plot!(x, -x .^ 3, label = "cubic") - @test PlotsBase._guess_best_legend_position(:best, pl) === :bottomleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :bottomleft x = [0, 1, 0, 1] y = [0, 0, 1, 1] pl = scatter(x, y, xlims = [0.0, 1.3], ylims = [0.0, 1.3], label = "test") - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright pl = scatter(x, y, xlims = [-0.3, 1.0], ylims = [-0.3, 1.0], label = "test") - @test PlotsBase._guess_best_legend_position(:best, pl) === :bottomleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :bottomleft pl = scatter(x, y, xlims = [0.0, 1.3], ylims = [-0.3, 1.0], label = "test") - @test PlotsBase._guess_best_legend_position(:best, pl) === :bottomright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :bottomright pl = scatter(x, y, xlims = [-0.3, 1.0], ylims = [0.0, 1.3], label = "test") - @test PlotsBase._guess_best_legend_position(:best, pl) === :topleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topleft y1 = [ 0.6640202072697099, @@ -250,48 +250,48 @@ end y2 = [0.40089741940615464, 0.6687326060649715, 0.6844117863127116] pl = plot(1:10, y1) pl = plot!(1:3, y2, xlims = (0, 10), ylims = (0, 1)) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright # test empty plot pl = plot([]) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright # test that we didn't overlap other placements - @test PlotsBase._guess_best_legend_position(:bottomleft, pl) === :bottomleft + @test PlotsBase._guess_best_legend_position(:bottomleft, pl) ≡ :bottomleft # test singleton pl = plot(1:1) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright # test cycling indexes x = 0.0:0.1:1 y = [1, 2, 3] pl = scatter(x, y) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright # Test step plot with variable limits x = 0:0.001:1 y = vcat([0.0 for _ in 1:100], [1.0 for _ in 101:200], [0.5 for _ in 201:1001]) pl = scatter(x, y) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright pl = scatter(x, y, xlims = [0, 0.25]) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topleft pl = scatter(x, y, xlims = [0.1, 0.25]) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright pl = scatter(x, y, xlims = [0.18, 0.25]) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright pl = scatter(x, y, ylims = [-1, 0.75]) - @test PlotsBase._guess_best_legend_position(:best, pl) === :bottomright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :bottomright pl = scatter(x, y, ylims = [0.25, 0.75]) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright pl = scatter(-x, y, ylims = [0.25, 0.75]) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright pl = scatter(-x, y) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topleft pl = scatter(-x, -y) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topleft + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topleft pl = scatter(x, -y) - @test PlotsBase._guess_best_legend_position(:best, pl) === :topright + @test PlotsBase._guess_best_legend_position(:best, pl) ≡ :topright end @testset "dispatch" begin diff --git a/Project.toml b/Project.toml index 430295e24..cb4539f3b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,24 +1,20 @@ name = "Plots" uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" author = ["Tom Breloff (@tbreloff)"] -version = "1.41.0" +version = "2.0.0" [deps] GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PlotsBase = "c52230a3-c5da-43a3-9e85-260fcdfdc737" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -Preferences = "21216c6a-2e73-6563-6e65-726566657250" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" [compat] -GR = "0.69.5 - 0.73" -Pkg = "1" -PlotsBase = "1.41" +GR = "0, 1" +PlotsBase = "0.1" PrecompileTools = "1" -Preferences = "1" -Reexport = "0.2, 1" -julia = "1.6" +Reexport = "1" +julia = "1.9" [extras] PythonPlot = "274fc56d-3b97-40fa-a1cd-1b4a50311bf9" diff --git a/RecipesBase/src/RecipesBase.jl b/RecipesBase/src/RecipesBase.jl index 5f3410aff..aa5103caa 100644 --- a/RecipesBase/src/RecipesBase.jl +++ b/RecipesBase/src/RecipesBase.jl @@ -73,10 +73,10 @@ _is_arrow_tuple(expr::Expr) = expr.head ≡ :tuple && !isempty(expr.args) && isa(expr.args[1], Expr) && - expr.args[1].head === :(-->) + expr.args[1].head ≡ :(-->) -_equals_symbol(x::Symbol, sym::Symbol) = x === sym -_equals_symbol(x::QuoteNode, sym::Symbol) = x.value === sym +_equals_symbol(x::Symbol, sym::Symbol) = x ≡ sym +_equals_symbol(x::QuoteNode, sym::Symbol) = x.value ≡ sym _equals_symbol(x, sym::Symbol) = false # build an apply_recipe function header from the recipe function header @@ -112,7 +112,7 @@ function create_kw_body(func_signature::Expr) if isa(arg1, Expr) && arg1.head ≡ :parameters for kwpair in arg1.args k, v = kwpair.args - if isa(k, Expr) && k.head === :(::) + if isa(k, Expr) && k.head ≡ :(::) k = k.args[1] @warn """ Type annotations on keyword arguments not currently supported in recipes. @@ -163,14 +163,14 @@ function process_recipe_body!(expr::Expr) # the unused operator `:=` will mean force: `x := 5` is equivalent to `x --> 5, force` # note: this means "x is defined as 5" - if e.head === :(:=) + if e.head ≡ :(:=) force = true e.head = :(-->) end # we are going to recursively swap out `a --> b, flags...` commands # note: this means "x may become 5" - if e.head === :(-->) + if e.head ≡ :(-->) k, v = e.args if isa(k, Symbol) k = QuoteNode(k) @@ -298,7 +298,7 @@ macro recipe(funcexpr::Expr) $cleanup_body series_list = $RecipesBase.RecipeData[] func_return = $func_body - func_return === nothing || push!( + func_return ≡ nothing || push!( series_list, $RecipesBase.RecipeData( plotattributes, diff --git a/RecipesPipeline/src/user_recipe.jl b/RecipesPipeline/src/user_recipe.jl index 4c0fcbaaa..00321d7ca 100644 --- a/RecipesPipeline/src/user_recipe.jl +++ b/RecipesPipeline/src/user_recipe.jl @@ -115,9 +115,9 @@ end @recipe function f(x, y, z) # COV_EXCL_LINE wrap_surfaces!(plotattributes, x, y, z) did_replace = false - did_replace |= x !== (newx = _apply_type_recipe(plotattributes, x, :x)) - did_replace |= y !== (newy = _apply_type_recipe(plotattributes, y, :y)) - did_replace |= z !== (newz = _apply_type_recipe(plotattributes, z, :z)) + did_replace |= x ≢ (newx = _apply_type_recipe(plotattributes, x, :x)) + did_replace |= y ≢ (newy = _apply_type_recipe(plotattributes, y, :y)) + did_replace |= z ≢ (newz = _apply_type_recipe(plotattributes, z, :z)) if did_replace newx, newy, newz else @@ -127,8 +127,8 @@ end @recipe function f(x, y) # COV_EXCL_LINE wrap_surfaces!(plotattributes, x, y) did_replace = false - did_replace |= x !== (newx = _apply_type_recipe(plotattributes, x, :x)) - did_replace |= y !== (newy = _apply_type_recipe(plotattributes, y, :y)) + did_replace |= x ≢ (newx = _apply_type_recipe(plotattributes, x, :x)) + did_replace |= y ≢ (newy = _apply_type_recipe(plotattributes, y, :y)) if did_replace newx, newy else @@ -137,7 +137,7 @@ end end @recipe function f(y) # COV_EXCL_LINE wrap_surfaces!(plotattributes, y) - if y !== (newy = _apply_type_recipe(plotattributes, y, :y)) + if y ≢ (newy = _apply_type_recipe(plotattributes, y, :y)) newy else SliceIt, nothing, y, nothing @@ -150,7 +150,7 @@ end did_replace = false newargs = map( v -> begin - did_replace |= v !== (newv = _apply_type_recipe(plotattributes, v, :unknown)) + did_replace |= v ≢ (newv = _apply_type_recipe(plotattributes, v, :unknown)) newv end, (v1, v2, v3, v4, vrest...), @@ -167,7 +167,7 @@ wrap_surfaces!(plotattributes, x::AVec, y::AVec, z::AMat) = wrap_surfaces!(plota wrap_surfaces!(plotattributes, x::AVec, y::AVec, z::Surface) = wrap_surfaces!(plotattributes) wrap_surfaces!(plotattributes) = - if (v = get(plotattributes, :fill_z, nothing)) !== nothing + if (v = get(plotattributes, :fill_z, nothing)) ≢ nothing v isa Surface || (plotattributes[:fill_z] = Surface(v)) end diff --git a/ci/downstream.jl b/ci/downstream.jl new file mode 100644 index 000000000..faf53f45f --- /dev/null +++ b/ci/downstream.jl @@ -0,0 +1,78 @@ +using Pkg + +const LibGit2 = Pkg.GitTools.LibGit2 +const TOML = Pkg.TOML + +failsafe_clone_checkout(path, url) = begin + local repo + for i in 1:6 + try + repo = Pkg.GitTools.ensure_clone(stdout, path, url) + break + catch err + @warn err + sleep(20i) + end + end + + @assert isfile(joinpath(path, "Project.toml")) "spurious network error: clone failed, bailing out" + + name, _ = splitext(basename(url)) + registries = joinpath(first(DEPOT_PATH), "registries") + general = joinpath(registries, "General") + versions = joinpath(general, name[1:1], name, "Versions.toml") + if !isfile(versions) + mkpath(general) + run(setenv(`tar xf $general.tar.gz`; dir = general)) + end + @assert isfile(versions) + + version_dict = TOML.parse(read(versions, String)) + stable = VersionNumber.(keys(version_dict)) |> maximum + tag = LibGit2.GitObject(repo, "v$stable") + hash = string(LibGit2.target(tag)) + LibGit2.checkout!(repo, hash) + nothing +end + +pkg_version(name) = + Pkg.Types.read_package(normpath(@__DIR__, "..", name, "Project.toml")).version |> string + +maybe_pin_version!(dict::AbstractDict, name::AbstractString, ver::AbstractString) = + haskey(dict, name) && (dict[name] = "=$ver") + +"fake supported Plots ecosystem versions for using `Pkg.develop`" +fake_supported_versions!(path) = begin + toml = joinpath(path, "Project.toml") + parsed_toml = TOML.parse(read(toml, String)) + compat = parsed_toml["compat"] + maybe_pin_version!(compat, "RecipesBase", pkg_version("RecipesBase")) + maybe_pin_version!(compat, "RecipesPipeline", pkg_version("RecipesPipeline")) + maybe_pin_version!(compat, "PlotsBase", pkg_version("PlotsBase")) + maybe_pin_version!(compat, "Plots", pkg_version("")) + open(toml, "w") do io + TOML.print(io, parsed_toml) + end + # print(read(toml, String)) # debug + nothing +end + +test_stable(pkg::AbstractString) = begin + Pkg.activate(; temp = true) + mktempdir() do tmpd + for dn in ("RecipesBase", "RecipesPipeline", "PlotsBase", "") + Pkg.develop(; path = joinpath(@__DIR__, "..", dn)) + end + + pkg_dir = joinpath(tmpd, "$pkg.jl") + failsafe_clone_checkout(pkg_dir, "https://github.com/JuliaPlots/$pkg.jl") + fake_supported_versions!(pkg_dir) + + Pkg.develop(; path = pkg_dir) + Pkg.test(pkg) + end + nothing +end + +test_stable("GraphRecipes") +test_stable("StatsPlots") diff --git a/ci/matplotlib.jl b/ci/matplotlib.jl new file mode 100644 index 000000000..4c657a3b0 --- /dev/null +++ b/ci/matplotlib.jl @@ -0,0 +1,25 @@ +using Pkg +Pkg.add("CondaPkg") + +using CondaPkg +CondaPkg.resolve() + +libgcc = if Sys.islinux() + # see discourse.julialang.org/t/glibcxx-version-not-found/82209/8 + # julia 1.8.3 is built with libstdc++.so.6.0.29, so we must restrict to this version (gcc 11.3.0, not gcc 12.2.0) + # see gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html + specs = Dict( + v"3.4.29" => ">=11.1,<12.1", + v"3.4.30" => ">=12.1,<13.1", + v"3.4.31" => ">=13.1,<14.1", + v"3.4.32" => ">=14.1,<15.1", + v"3.4.33" => ">=15.1,<16.1", + # ... keep this up-to-date with gcc 16 + )[Base.BinaryPlatforms.detect_libstdcxx_version()] + ("libgcc-ng$specs", "libstdcxx-ng$specs") +else + () +end + +CondaPkg.PkgREPL.add([libgcc..., "matplotlib"]) +CondaPkg.status() diff --git a/src/Plots.jl b/src/Plots.jl index 3d9c6e9f6..dace3c1fd 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -1,121 +1,92 @@ module Plots + using PrecompileTools -using Preferences using Reexport -using Pkg @reexport using PlotsBase -function __init__() - ccall(:jl_generating_output, Cint, ()) == 1 && return - load_default_backend() -end - -# from github.com/JuliaPackaging/Preferences.jl/blob/master/README.md: -# "Preferences that are accessed during compilation are automatically marked as compile-time preferences" -# ==> this must always be done during precompilation, otherwise -# the cache will not invalidate when preferences change -const PLOTS_DEFAULT_BACKEND = lowercase(load_preference(Plots, "default_backend", "gr")) - -function load_default_backend() - # environment variable preempts the `Preferences` based mechanism - PlotsBase.CURRENT_BACKEND.sym = - get(ENV, "PLOTS_DEFAULT_BACKEND", PLOTS_DEFAULT_BACKEND) |> lowercase |> Symbol - if (pkg_name = PlotsBase.backend_package_name()) ≡ :GR - @eval import GR - end - Base.invokelatest(PlotsBase.backend, PlotsBase.CURRENT_BACKEND.sym) +if PlotsBase.DEFAULT_BACKEND == "gr" + @debug "loading default GR" + import GR end -function set_default_backend!( - backend::Union{Nothing,AbstractString,Symbol} = nothing; - force = true, - kw..., -) - if backend ≡ nothing - delete_preferences!(Plots, "default_backend"; force, kw...) - else - # NOTE: `_check_installed` already throws a warning - if (value = lowercase(string(backend))) |> PlotsBase._check_installed ≢ nothing - set_preferences!(Plots, "default_backend" => value; force, kw...) - end - end - nothing -end +function __init__() + ccall(:jl_generating_output, Cint, ()) == 1 && return + PlotsBase.default_backend() -function diagnostics(io::IO = stdout) - origin = if has_preference(Plots, "default_backend") - "`Preferences`" - elseif haskey(ENV, "PLOTS_DEFAULT_BACKEND") - "environment variable" - else - "fallback" - end - if (be = backend_name()) ≡ :none - @info "no `Plots` backends currently initialized" - else - be_name = string(PlotsBase.backend_package_name(be)) - @info "selected `Plots` backend: $be_name, from $origin" - Pkg.status( - ["Plots", "PlotsBase", "RecipesBase", "RecipesPipeline", be_name]; - mode = Pkg.PKGMODE_MANIFEST, - io, - ) - end nothing end # COV_EXCL_START -@setup_workload begin - load_default_backend() - @debug PlotsBase.backend_package_name() - n = length(PlotsBase._examples) - imports = sizehint!(Expr[], n) - examples = sizehint!(Expr[], 10n) - scratch_dir = mktempdir() - for i in setdiff( - 1:n, - PlotsBase._backend_skips[backend_name()], - PlotsBase._animation_examples, - ) - PlotsBase._examples[i].external && continue - (imp = PlotsBase._examples[i].imports) ≡ nothing || - push!(imports, PlotsBase.replace_module(imp)) - func = gensym(string(i)) - push!( - examples, - quote - $func() = begin # evaluate each example in a local scope - $(PlotsBase._examples[i].exprs) - $i == 1 || return # only for one example - fn = joinpath(scratch_dir, tempname()) - pl = current() - show(devnull, pl) - # FIXME: pgfplotsx requires bug - backend_name() ≡ :pgfplotsx && return - if backend_name() ≡ :unicodeplots - savefig(pl, "$fn.txt") - return - end - showable(MIME"image/png"(), pl) && savefig(pl, "$fn.png") - showable(MIME"application/pdf"(), pl) && savefig(pl, "$fn.pdf") - if showable(MIME"image/svg+xml"(), pl) - show(IOBuffer(), MIME"image/svg+xml"(), pl) - end - nothing - end - $func() - end, +if PlotsBase.DEFAULT_BACKEND == "gr" # FIXME: Creating a new global in closed module `Main` (`UnicodePlots`) breaks incremental compilation because the side effects will not be permanent. + @setup_workload begin + #= + if PlotsBase.DEFAULT_BACKEND == "gr" + import GR + elseif PlotsBase.DEFAULT_BACKEND == "unicodeplots" + @eval Main import UnicodePlots + elseif PlotsBase.DEFAULT_BACKEND == "pythonplot" + @eval Main import PythonPlot + elseif PlotsBase.DEFAULT_BACKEND == "pgfplotsx" + @eval Main import PGFPlotsX + elseif PlotsBase.DEFAULT_BACKEND == "plotlyjs" + @eval Main import PlotlyJS + elseif PlotsBase.DEFAULT_BACKEND == "gaston" + @eval Main import Gaston + elseif PlotsBase.DEFAULT_BACKEND == "hdf5" + @eval Main import HDF5 + end + =# + PlotsBase.default_backend() + @debug PlotsBase.backend_package_name() + n = length(PlotsBase._examples) + imports = sizehint!(Expr[], n) + examples = sizehint!(Expr[], 10n) + scratch_dir = mktempdir() + for i in setdiff( + 1:n, + PlotsBase._backend_skips[backend_name()], + PlotsBase._animation_examples, ) - end - withenv("GKSwstype" => "nul") do - @compile_workload begin - load_default_backend() - eval.(imports) - eval.(examples) + PlotsBase._examples[i].external && continue + (imp = PlotsBase._examples[i].imports) ≡ nothing || + push!(imports, PlotsBase.replace_module(imp)) + func = gensym(string(i)) + push!( + examples, + quote + $func() = begin # evaluate each example in a local scope + $(PlotsBase._examples[i].exprs) + $i == 1 || return # trigger display only for one example + fn = joinpath(scratch_dir, tempname()) + pl = current() + show(devnull, pl) + # FIXME: pgfplotsx requires bug + backend_name() ≡ :pgfplotsx && return + if backend_name() ≡ :unicodeplots + savefig(pl, "$fn.txt") + return + end + showable(MIME"image/png"(), pl) && savefig(pl, "$fn.png") + showable(MIME"application/pdf"(), pl) && savefig(pl, "$fn.pdf") + if showable(MIME"image/svg+xml"(), pl) + show(PipeBuffer(), MIME"image/svg+xml"(), pl) + end + nothing + end + $func() + end, + ) end + withenv("GKSwstype" => "nul", "MPLBACKEND" => "agg") do + @compile_workload begin + PlotsBase.default_backend() + eval.(imports) + eval.(examples) + end + end + PlotsBase.CURRENT_PLOT.nullableplot = nothing end - PlotsBase.CURRENT_PLOT.nullableplot = nothing end # COV_EXCL_STOP -end +end # module diff --git a/test/preferences.jl b/test/preferences.jl deleted file mode 100644 index 6f5101ffb..000000000 --- a/test/preferences.jl +++ /dev/null @@ -1,75 +0,0 @@ - -@testset "Preferences" begin - Plots.set_default_backend!() # start with empty preferences - - withenv("PLOTS_DEFAULT_BACKEND" => "invalid") do - @test_logs (:error, r"Unsupported backend.*") Plots.load_default_backend() - end - @test_logs (:error, r"Unsupported backend.*") backend(:invalid) - - @test Plots.load_default_backend() == Base.get_extension(PlotsBase, :GRExt).GRBackend() - - withenv("PLOTS_DEFAULT_BACKEND" => "unicodeplots") do - @test_logs (:info, r".*environment variable") Plots.diagnostics(devnull) - @test Plots.load_default_backend() == - Base.get_extension(PlotsBase, :UnicodePlotsExt).UnicodePlotsBackend() - end - - @test Plots.load_default_backend() == Base.get_extension(PlotsBase, :GRExt).GRBackend() - @test Plots.PlotsBase.backend_package_name() ≡ :GR - @test Plots.backend_name() ≡ :gr - - @test_logs (:info, r".*fallback") Plots.diagnostics(devnull) - - @test Plots.PlotsBase.merge_with_base_supported([:annotations, :guide]) isa Set - @test Plots.PlotsBase.CurrentBackend(:gr).sym ≡ :gr - - @test_logs (:warn, r".*is not compatible with") Plots.set_default_backend!(:invalid) - - @testset "persistent backend" begin - # this test mimics a restart, which is needed after a preferences change - Plots.set_default_backend!(:unicodeplots) - script = tempname() - write( - script, - """ - using Pkg, Test; io = (devnull, stdout)[1] # toggle for debugging - Pkg.activate(; temp = true, io) - Pkg.develop(; path = "$(escape_string(pkgdir(Plots)))", io) - Pkg.add("UnicodePlots"; io) # checked by Plots - import UnicodePlots - using Plots - res = @testset "Preferences UnicodePlots" begin - @test_logs (:info, r".*Preferences") Plots.diagnostics(io) - @test backend() == Base.get_extension(PlotsBase, :UnicodePlotsExt).UnicodePlotsBackend() - end - exit(res.n_passed == 2 ? 0 : 123) - """, - ) - @test success(run(```$(Base.julia_cmd()) $script```)) - end - - is_pkgeval() || for pkg in TEST_PACKAGES - be = Symbol(lowercase(pkg)) - (Sys.isapple() && be ≡ :gaston) && continue # FIXME: hangs - (Sys.iswindows() && be ≡ :plotlyjs && is_ci()) && continue # FIXME: OutOfMemory - @test_logs Plots.set_default_backend!(be) # test the absence of warnings - rm.(Base.find_all_in_cache_path(Base.module_keys[Plots])) # make sure the compiled cache is removed - script = tempname() - write( - script, - """ - import $pkg - using Test, Plots - $be() - res = @testset "Persistent backend $pkg" begin - @test Plots.backend_name() ≡ :$be - end - exit(res.n_passed == 1 ? 0 : 123) - """, - ) - @test success(run(```$(Base.julia_cmd()) $script```)) # test default precompilation - end - - Plots.set_default_backend!() # clear `Preferences` key -end diff --git a/test/runtests.jl b/test/runtests.jl index 054a40117..c1d2b40b1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,29 +1,28 @@ const TEST_PACKAGES = - strip.(split(get(ENV, "PLOTS_TEST_PACKAGES", "GR,UnicodePlots,PythonPlot"), ",")) + let val = get(ENV, "PLOTS_TEST_PACKAGES", "GR,UnicodePlots,PythonPlot") + Symbol.(strip.(split(val, ","))) + end +const TEST_BACKENDS = NamedTuple(p => Symbol(lowercase(string(p))) for p in TEST_PACKAGES) + using PlotsBase # initialize all backends for pkg in TEST_PACKAGES - @eval import $(Symbol(pkg)) # trigger extension - getproperty(PlotsBase, Symbol(lowercase(pkg)))() + @eval begin + import $pkg # trigger extension + $(TEST_BACKENDS[pkg])() + end end gr() -using Preferences using Plots using Test -is_auto() = Plots.PlotsBase.bool_env("VISUAL_REGRESSION_TESTS_AUTO") -is_pkgeval() = Plots.PlotsBase.bool_env("JULIA_PKGEVAL") -is_ci() = Plots.PlotsBase.bool_env("CI") - -# get `Preferences` set backend, if any -const PREVIOUS_DEFAULT_BACKEND = load_preference(Plots, "default_backend") - -include("preferences.jl") - -if PREVIOUS_DEFAULT_BACKEND === nothing - delete_preferences!(Plots, "default_backend") # restore the absence of a preference -else - Plots.set_default_backend!(PREVIOUS_DEFAULT_BACKEND) # reset to previous state +for pkg in TEST_PACKAGES + @testset "simple plots using $pkg" begin + @eval $(TEST_BACKENDS[pkg])() + pl = plot(1:2) + @test pl isa PlotsBase.Plot + show(devnull, pl) + end end